diff --git a/.forceignore b/.forceignore
index 4923e15..b0ff555 100755
--- a/.forceignore
+++ b/.forceignore
@@ -12,6 +12,11 @@ package.xml
**/__tests__/signals.test.ts
appMenus/
-dashboards/
-reports/
-reportTypes/
+# Reports and Report Types
+**/reports/**
+**/reportTypes/**
+
+# Dashboards
+**/dashboards/**
+
+**/tw/input.css
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78b4c7f..628ee1c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Use Node.js ${{ matrix.node-version }}
+ - name: Setup node
uses: actions/setup-node@v3
with:
- node-version: ${{ matrix.node-version }}
+ node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run build --if-present
diff --git a/.prettierrc b/.prettierrc
index 18039a0..f4bf55e 100755
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,7 +1,6 @@
{
"trailingComma": "none",
"plugins": [
- "prettier-plugin-apex",
"@prettier/plugin-xml"
],
"overrides": [
diff --git a/README.md b/README.md
index d34217e..28a01b3 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ A simple yet powerful reactive state management solution for Lightning Web Compo
Inspired by the Signals technology behind SolidJs, Preact, Svelte 5 Runes and the Vue 3 Composition API, LWC Signals is
a
-reactive signals for Lightning Web Components that allows you to create reactive data signalss
+reactive signals for Lightning Web Components that allows you to create reactive data signals
that can be used to share state between components.
It features:
@@ -19,6 +19,22 @@ It features:
- 🔬️ **Small Surface** The API does not offer more than what is needed, keeping the learning curve and bloat to a
minimum
+The goal is to allow you to create beautiful and complex user experiences, while achieving clean code that
+separates concerns and is easy to maintain!
+
+Easily implement:
+
+- Reactive data stores
+- Undo
+- Optimistic updates
+- Data caching through storage (localStorage, cookies, etc)
+
+
+
+
+
+> To see the code for the example above, check the `examples/shopping-cart` folder.
+
# Getting Started
Copy the `force-app/lwc/signals` folder to your project.
@@ -124,9 +140,9 @@ export default class Display extends LightningElement {
> to trigger the reactivity. This is because we need the value to be reassigned so that
> LWC reactive system can detect the change and update the UI.
-
+
-
+
### Stacking computed values
@@ -203,7 +219,7 @@ export default class ContactInfoForm extends LightningElement {
}
```
-**You can create a computed value that depends on both signalss**
+**You can create a computed value that depends on both signals**
```html
@@ -221,7 +237,7 @@ export default class ContactInfoForm extends LightningElement {
// businessCard.js
import { LightningElement } from "lwc";
import { $computed } from "c/signals";
-import { accountName, contactName } from "c/demoSignalss";
+import { accountName, contactName } from "c/demoSignals";
export default class BusinessCard extends LightningElement {
contactInfo = $computed(
@@ -234,9 +250,9 @@ export default class BusinessCard extends LightningElement {
}
```
-
+
-
+
> ❗ Notice that we are using a property instead of a getter in the `$computed` callback function, because
> we need to reassign the value to `this.contactInfo` to trigger the reactivity, as it is a complex object.
@@ -346,9 +362,9 @@ type AsyncData = {
> 🍪 One benefit of using the `$resource` over declarative Apex or `@wire` is that it keeps track of the loading
> state for you, which saves you the effort of having to calculate it yourself.
-
+
-
+
---
@@ -421,7 +437,7 @@ Let's now create our picklist component that allows the user to select an accoun
// accountPicker.js
import { LightningElement, track, wire } from "lwc";
import getAccounts from "@salesforce/apex/ResourceController.getAccounts";
-import { selectedAccountId } from "c/demoSignalss";
+import { selectedAccountId } from "c/demoSignals";
export default class AccountPicker extends LightningElement {
@track accounts = [];
@@ -486,16 +502,16 @@ Now, let's create the component that displays the details of the selected accoun
// accountDetails.js
import { LightningElement } from "lwc";
import { $computed } from "c/signals";
-import { getAccount } from "c/demoSignalss";
+import { getAccount } from "c/demoSignals";
export default class AccountDetails extends LightningElement {
account = $computed(() => (this.account = getAccount.value)).value;
}
```
-
+
-
+
> 🍪 One extra feature of the data returned by the `$resource` function is that when it is reloading the data, the
> previous data is still available in the `data` property. This allows you to keep the old value while the new value is
@@ -550,6 +566,93 @@ export default class ContactList extends LightningElement {
}
```
+### Mutating `$resource` data
+
+Besides `refetch`, the `$resource` function also returns a `mutate` function that allows you to mutate the data.
+
+`mutate` is useful when you want to update the data without refetching it (and avoid a trip to the server).
+
+It receives a single value, which will be set as the new value of the data. The `resource` value will be updated
+immediately, the `.loading` property will be set to `false`, and the `.error` property will be set to `null`.
+
+```javascript
+import { $resource } from "c/signals";
+
+const { data, mutate } = $resource(asyncFunction);
+
+mutate("new value");
+```
+
+#### Reacting to mutated values
+
+When using the `mutate` function, you might want to react to the changes in the data. For example, you might now
+want to call an Apex function to save the new value to the server, and make sure the data is synced.
+
+For this, you can provide a function through the options object's `onMutate`.
+
+The function you provide can receive 3 arguments:
+
+- The new value
+- The old value
+- A `mutate` function that you can use the update the data again. This can be used for when you want to update the data
+ based on what was returned from the server, but you don't want to refetch the data. You SHOULD use this mutate
+ function over the one returned when creating the `$resource` because this will not trigger `onMutate` once again.
+
+```javascript
+import { $resource } from "c/signals";
+import getContacts from "@salesforce/apex/ContactController.getContacts";
+import saveContacts from "@salesforce/apex/ContactController.saveContacts";
+
+const { data, mutate } = $resource(
+ getContacts,
+ {},
+ {
+ onMutate: async (newValue, oldValue, mutate) => {
+ await saveContacts({ contacts: newValue });
+ mutate(newValue);
+ }
+ }
+);
+```
+
+In the case an error occurs on your server call, the `mutate` you can pass an error object as the second argument to
+the `mutate` function. This will set the `.error` property of the `resource` to the error object.
+
+```javascript
+import { $resource } from "c/signals";
+
+const { data, mutate } = $resource(asyncFunction);
+
+try {
+ await saveContacts({ contacts: newValue });
+ mutate(newValue);
+} catch (error) {
+ mutate(null, error);
+}
+```
+
+#### Optimistic updating
+
+When you mutate a `resource` as exemplified above, you can achieve the concept of optimistic updating. This is when you
+update the value immediately before the server responds, and then update the value again when the server responds.
+
+Optimistically updating the value can provide a better user experience by making the UI feel more responsive, but it
+can also lead to inconsistencies if the server responds with an error. So if you wish to turn this off, and
+manage updating the value yourself, either by `refetching` or by using an `onMutate` function, you can set the
+`optimisticMutate` option to `false`.
+
+```javascript
+import { $resource } from "c/signals";
+
+const { data, refetch, mutate } = $resource(
+ asyncFunction,
+ {},
+ {
+ optimisticMutate: false
+ }
+);
+```
+
## Storage
By default, any created signal is stored in memory and will be lost when the component is destroyed. This behavior
@@ -568,7 +671,7 @@ The following storage helpers are available by default:
- `useCookies(key: string, expires?: Date)`: Stores the signal in a cookie with the given key. You can also pass an
optional `expires` parameter to set the expiration date of the cookie
-### Creating custom storage
+### Creating a custom storage
The `storage` option receives a function that defines the behavior for where the data should be stored.
This means you can create your own custom storage solution by passing a function with the following
@@ -633,6 +736,24 @@ storage, and the setter should set the value in the storage.
Notice that any additional properties you add to the object returned by `createStorage` will be available in the
returned object. That is how we can add the `undo` function to the `counter` signal and use it to undo the changes.
+## Examples
+
+You can find full working examples in the `examples` folder.
+
+For a full kitchen sink example that combines all the concepts, you can check the `shopping-cart` example.
+
+It includes:
+
+- Getting data from the server
+- Optimistic updates by updating the local value on change
+- Re-updating the value when the server responds
+- Undo functionality by storing the state history in a custom signal
+- Caching the data in the `localStorage` for a fast first load.
+
+
+
+
+
# Contributing
Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for more information.
diff --git a/doc-assets/full-example.gif b/doc-assets/full-example.gif
new file mode 100644
index 0000000..913d4c7
Binary files /dev/null and b/doc-assets/full-example.gif differ
diff --git a/doc-assets/kitchen-sink.gif b/doc-assets/kitchen-sink.gif
new file mode 100644
index 0000000..f00db2a
Binary files /dev/null and b/doc-assets/kitchen-sink.gif differ
diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html
index 3872b64..3aaca44 100644
--- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html
+++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.html
@@ -6,4 +6,4 @@
Contact Name: {contactInfo.contactName}
-
+
\ No newline at end of file
diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js
index f3277cc..622417a 100644
--- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js
+++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js
@@ -10,4 +10,4 @@ export default class BusinessCard extends LightningElement {
contactName: contactName.value
})
).value;
-}
+}
\ No newline at end of file
diff --git a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml
index 5e6eba0..acf1f27 100644
--- a/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml
+++ b/examples/computed-from-multiple-signals/lwc/businessCard/businessCard.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html
index 40160b0..83b1d89 100644
--- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html
+++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.html
@@ -10,4 +10,4 @@
value={contactName}
onchange={handleContactNameChange}
>
-
+
\ No newline at end of file
diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js
index a801bc2..d948c82 100644
--- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js
+++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js
@@ -13,4 +13,4 @@ export default class ContactInfoForm extends LightningElement {
handleContactNameChange(event) {
contactName.value = event.target.value;
}
-}
+}
\ No newline at end of file
diff --git a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml
index 82f0043..953579a 100644
--- a/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml
+++ b/examples/computed-from-multiple-signals/lwc/contactInfoForm/contactInfoForm.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/counter/lwc/countChanger/countChanger.html b/examples/counter/lwc/countChanger/countChanger.html
index 06b510b..77b37c1 100644
--- a/examples/counter/lwc/countChanger/countChanger.html
+++ b/examples/counter/lwc/countChanger/countChanger.html
@@ -3,4 +3,4 @@
Decrement
Increment
-
+
\ No newline at end of file
diff --git a/examples/counter/lwc/countChanger/countChanger.js b/examples/counter/lwc/countChanger/countChanger.js
index 4b172b8..09a3099 100644
--- a/examples/counter/lwc/countChanger/countChanger.js
+++ b/examples/counter/lwc/countChanger/countChanger.js
@@ -9,4 +9,4 @@ export default class CountChanger extends LightningElement {
decrementCount() {
counter.value--;
}
-}
+}
\ No newline at end of file
diff --git a/examples/counter/lwc/countChanger/countChanger.js-meta.xml b/examples/counter/lwc/countChanger/countChanger.js-meta.xml
index 545a169..c5a6356 100644
--- a/examples/counter/lwc/countChanger/countChanger.js-meta.xml
+++ b/examples/counter/lwc/countChanger/countChanger.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/counter/lwc/countTracker/countTracker.html b/examples/counter/lwc/countTracker/countTracker.html
index 44645ee..674de33 100644
--- a/examples/counter/lwc/countTracker/countTracker.html
+++ b/examples/counter/lwc/countTracker/countTracker.html
@@ -1,4 +1,4 @@
The current count is ($computed reactive property): {reactiveProperty}
The counter plus two value is (nested computed): {counterPlusTwo}
-
+
\ No newline at end of file
diff --git a/examples/counter/lwc/countTracker/countTracker.js b/examples/counter/lwc/countTracker/countTracker.js
index db10620..6329155 100644
--- a/examples/counter/lwc/countTracker/countTracker.js
+++ b/examples/counter/lwc/countTracker/countTracker.js
@@ -8,4 +8,4 @@ export default class CountTracker extends LightningElement {
counterPlusTwo = $computed(() => (this.counterPlusTwo = counterPlusTwo.value))
.value;
-}
+}
\ No newline at end of file
diff --git a/examples/counter/lwc/countTracker/countTracker.js-meta.xml b/examples/counter/lwc/countTracker/countTracker.js-meta.xml
index 256254a..e01574c 100644
--- a/examples/counter/lwc/countTracker/countTracker.js-meta.xml
+++ b/examples/counter/lwc/countTracker/countTracker.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/demo-signals/lwc/demoSignals/apex-fetcher.js b/examples/demo-signals/lwc/demoSignals/apex-fetcher.js
index 55b0f9c..598d6af 100644
--- a/examples/demo-signals/lwc/demoSignals/apex-fetcher.js
+++ b/examples/demo-signals/lwc/demoSignals/apex-fetcher.js
@@ -6,7 +6,7 @@ export const { data: fetchContacts } = $resource(getContacts);
export const selectedAccountId = $signal(null);
-$effect(() => console.log(selectedAccountId.value));
+$effect(() => console.log('selected Account Id', selectedAccountId.value));
export const { data: getAccount } = $resource(getAccountDetails, () => ({
accountId: selectedAccountId.value
diff --git a/examples/demo-signals/lwc/demoSignals/contact-info.js b/examples/demo-signals/lwc/demoSignals/contact-info.js
index ee69883..f51de82 100644
--- a/examples/demo-signals/lwc/demoSignals/contact-info.js
+++ b/examples/demo-signals/lwc/demoSignals/contact-info.js
@@ -2,4 +2,4 @@ import { $signal } from "c/signals";
export const accountName = $signal("ACME");
-export const contactName = $signal("John Doe");
+export const contactName = $signal("John Doe");
\ No newline at end of file
diff --git a/examples/demo-signals/lwc/demoSignals/counter.js b/examples/demo-signals/lwc/demoSignals/counter.js
index 9456e03..d1feb75 100644
--- a/examples/demo-signals/lwc/demoSignals/counter.js
+++ b/examples/demo-signals/lwc/demoSignals/counter.js
@@ -1,5 +1,15 @@
import { $signal, $effect, $computed, useLocalStorage } from "c/signals";
+// EXAMPLE OF DEFAULT COUNTER
+
+// export const counter = $signal(0);
+
+// EXAMPLE OF COUNTER USING LOCAL STORAGE
+
+export const counter = $signal(0, {
+ storage: useLocalStorage("counter")
+});
+
// EXAMPLE OF COUNTER USING COOKIES
// let tomorrow = new Date();
@@ -9,13 +19,7 @@ import { $signal, $effect, $computed, useLocalStorage } from "c/signals";
// storage: useCookies("counter", tomorrow)
// });
-// EXAMPLE OF COUNTER USING LOCAL STORAGE
-
-export const counter = $signal(0, {
- storage: useLocalStorage("counter")
-});
-
$effect(() => console.log(counter.value));
export const counterPlusOne = $computed(() => counter.value + 1);
-export const counterPlusTwo = $computed(() => counterPlusOne.value + 1);
+export const counterPlusTwo = $computed(() => counterPlusOne.value + 1);
\ No newline at end of file
diff --git a/examples/demo-signals/lwc/demoSignals/demoSignals.js b/examples/demo-signals/lwc/demoSignals/demoSignals.js
index ce35dd8..e1fac1d 100644
--- a/examples/demo-signals/lwc/demoSignals/demoSignals.js
+++ b/examples/demo-signals/lwc/demoSignals/demoSignals.js
@@ -1,3 +1,4 @@
export * from "./counter";
export * from "./contact-info";
export * from "./apex-fetcher";
+export * from "./shopping-cart";
diff --git a/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml b/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml
index 175ca14..9cc72ec 100644
--- a/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml
+++ b/examples/demo-signals/lwc/demoSignals/demoSignals.js-meta.xml
@@ -4,4 +4,4 @@
Demo Signals
false
Demo Signals
-
+
\ No newline at end of file
diff --git a/examples/demo-signals/lwc/demoSignals/shopping-cart.js b/examples/demo-signals/lwc/demoSignals/shopping-cart.js
new file mode 100644
index 0000000..eeeb16f
--- /dev/null
+++ b/examples/demo-signals/lwc/demoSignals/shopping-cart.js
@@ -0,0 +1,80 @@
+import { $signal, $resource, $effect, useLocalStorage } from "c/signals";
+import getShoppingCart from "@salesforce/apex/ShoppingCartController.getShoppingCart";
+import updateShoppingCart from "@salesforce/apex/ShoppingCartController.updateShoppingCart";
+
+/**
+ * @typedef {Object} ShoppingCart
+ * @property {Item[]} items
+ */
+
+/**
+ * @typedef {Object} Item
+ * @property {string} id
+ * @property {string} name
+ * @property {string[]} properties
+ * @property {number} quantity
+ * @property {number} price
+ * @property {number} taxAmount
+ * @property {number} imgUrl
+ */
+
+// Store each state change in the cart history
+export const cartHistory = $signal([]);
+let isUndoing = false;
+
+export const undoCartChange = () => {
+ isUndoing = true;
+ const lastState = cartHistory.value[cartHistory.value.length - 1];
+ // Remove the last state from the history
+ cartHistory.value = cartHistory.value.slice(0, -1);
+ if (lastState) {
+ updateCart(lastState);
+ }
+ isUndoing = false;
+};
+
+/**
+ * Updates the cart on the server
+ * @param {ShoppingCart} newCart
+ * @param {ShoppingCart} previousValue
+ * @param mutate
+ */
+async function updateCartOnTheServer(newCart, previousValue, mutate) {
+ try {
+ // Keep track of the isUndoing value before making any async changes
+ // to ensure we don't update the history when undoing, even after
+ // an async operation.
+ const shouldUpdateHistory = !isUndoing;
+ // Update the cart on the server
+ const updatedShoppingCart = await updateShoppingCart({
+ newItems: newCart.items
+ });
+
+ // Update the local state with the new cart received from the server
+ mutate(updatedShoppingCart);
+
+ // Store the previous value in the history
+ if (shouldUpdateHistory) {
+ cartHistory.value = [...cartHistory.value, previousValue];
+ }
+ } catch (error) {
+ mutate(null, error);
+ }
+}
+
+const cachedCart = $signal(null, {
+ storage: useLocalStorage("shoppingCart")
+});
+
+export const { data: shoppingCart, mutate: updateCart } = $resource(
+ getShoppingCart,
+ {},
+ {
+ initialValue: cachedCart.value,
+ onMutate: updateCartOnTheServer
+ }
+);
+
+$effect(() => {
+ cachedCart.value = shoppingCart.value;
+});
diff --git a/examples/main/default/audience/Default_examples.audience-meta.xml b/examples/main/default/audience/Default_examples.audience-meta.xml
new file mode 100644
index 0000000..ca4c207
--- /dev/null
+++ b/examples/main/default/audience/Default_examples.audience-meta.xml
@@ -0,0 +1,8 @@
+
+
+ Default
+ examples
+
+ AllCriteriaMatch
+ true
+
diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.cmp b/examples/main/default/aura/forgotPassword/forgotPassword.cmp
new file mode 100644
index 0000000..4ad500e
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPassword.cmp
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml b/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml
new file mode 100644
index 0000000..b1f4b7a
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPassword.cmp-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Sample Component for forgotPassword
+
diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.css b/examples/main/default/aura/forgotPassword/forgotPassword.css
new file mode 100644
index 0000000..5d3bb22
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPassword.css
@@ -0,0 +1,76 @@
+.THIS #sfdc_username_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_user{
+ float:left;
+ width:23px;
+ height:25px;
+ padding-top:1px;
+ padding-left:2px;
+ margin:0px;
+
+}
+
+.THIS .login-icon {
+ color:#ccc;font-size:22px;
+}
+
+.THIS button.sfdc_button {
+ width: 100%;
+ margin-top: 15px;
+ margin-bottom: 5px;
+ color: #fff;
+ background-color: #0070d2;
+ border-color: #357ebd;
+ display: inline-block;
+ text-align: center;
+ vertical-align: middle;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 10px 12px;
+ font-size: 16px;
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 300;
+ line-height: 1.42857143;
+ border-radius: 2px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+.THIS button:hover {
+ background-color:#3276b1;
+ border-color:#285e8e;
+ cursor:pointer;
+}
+
+.THIS input {
+ margin-left:10px;
+ margin-top: 3px;
+ border: 0px solid transparent;
+ width: 70%;
+ -webkit-appearance: none;
+ font-size: 14px;
+}
+
+.THIS #error {
+ text-align: center;
+ color:#FF0000;
+}
+
+.THIS a {
+ color:white;
+ text-decoration: none;
+}
+.THIS a:hover {
+ color:white;
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/examples/main/default/aura/forgotPassword/forgotPassword.design b/examples/main/default/aura/forgotPassword/forgotPassword.design
new file mode 100644
index 0000000..38ebfe8
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPassword.design
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/forgotPassword/forgotPasswordController.js b/examples/main/default/aura/forgotPassword/forgotPasswordController.js
new file mode 100644
index 0000000..a456ace
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPasswordController.js
@@ -0,0 +1,23 @@
+({
+ handleForgotPassword: function (component, event, helpler) {
+ helpler.handleForgotPassword(component, event, helpler);
+ },
+ onKeyUp: function(component, event, helpler){
+ //checks for "enter" key
+ if (event.getParam('keyCode')===13) {
+ helpler.handleForgotPassword(component, event, helpler);
+ }
+ },
+
+ setExpId: function (component, event, helper) {
+ var expId = event.getParam('expid');
+ if (expId) {
+ component.set("v.expid", expId);
+ }
+ helper.setBrandingCookie(component, event, helper);
+ },
+
+ initialize: function(component, event, helper) {
+ $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire();
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js b/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js
new file mode 100644
index 0000000..85467fb
--- /dev/null
+++ b/examples/main/default/aura/forgotPassword/forgotPasswordHelper.js
@@ -0,0 +1,30 @@
+({
+ qsToEventMap: {
+ 'expid' : 'e.c:setExpId'
+ },
+
+ handleForgotPassword: function (component, event, helpler) {
+ var username = component.find("username").get("v.value");
+ var checkEmailUrl = component.get("v.checkEmailUrl");
+ var action = component.get("c.forgotPassword");
+ action.setParams({username:username, checkEmailUrl:checkEmailUrl});
+ action.setCallback(this, function(a) {
+ var rtnValue = a.getReturnValue();
+ if (rtnValue != null) {
+ component.set("v.errorMessage",rtnValue);
+ component.set("v.showError",true);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ setBrandingCookie: function (component, event, helpler) {
+ var expId = component.get("v.expid");
+ if (expId) {
+ var action = component.get("c.setExperienceId");
+ action.setParams({expId:expId});
+ action.setCallback(this, function(a){ });
+ $A.enqueueAction(action);
+ }
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/loginForm/loginForm.cmp b/examples/main/default/aura/loginForm/loginForm.cmp
new file mode 100644
index 0000000..189eff4
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginForm.cmp
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml b/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml
new file mode 100644
index 0000000..bcac32b
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginForm.cmp-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Sample Component for loginForm
+
diff --git a/examples/main/default/aura/loginForm/loginForm.css b/examples/main/default/aura/loginForm/loginForm.css
new file mode 100644
index 0000000..5b06d29
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginForm.css
@@ -0,0 +1,103 @@
+.THIS #sfdc_username_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_user{
+ float:left;
+ width:23px;
+ height:25px;
+ padding-top:1px;
+ padding-left:2px;
+ margin:0px;
+
+}
+
+.THIS #sfdc_lock{
+ float:left;
+ width:23px;
+ height:25px;
+ padding-top:1px;
+ padding-left:2px;
+ margin:0px;
+}
+
+.THIS #error {
+ text-align: center;
+ color:#FF0000;
+}
+
+.THIS .login-icon {
+ color:#ccc;font-size:22px;
+}
+
+.THIS #sfdc_password_container{
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS button.sfdc_button {
+ width: 100%;
+ margin-top: 15px;
+ margin-bottom: 5px;
+ color: #fff;
+ background-color: #0070d2;
+ border-color: #357ebd;
+ display: inline-block;
+ text-align: center;
+ vertical-align: middle;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 10px 12px;
+ font-size: 16px;
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 300;
+ line-height: 1.42857143;
+ border-radius: 2px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+.THIS button:hover {
+ background-color:#3276b1;
+ border-color:#285e8e;
+ cursor:pointer;
+}
+
+.THIS button .label {
+ color: #fff;
+}
+
+.THIS input {
+ margin-left:10px;
+ margin-top: 3px;
+ border: 0px solid transparent;
+ width: 70%;
+ -webkit-appearance: none;
+ font-size: 14px;
+}
+
+
+.THIS a {
+ color:white;
+ text-decoration: none;
+}
+.THIS a:hover {
+ color:white;
+ text-decoration: none;
+}
+
+.THIS label.uiLabel-hidden {
+ display:none;
+}
\ No newline at end of file
diff --git a/examples/main/default/aura/loginForm/loginForm.design b/examples/main/default/aura/loginForm/loginForm.design
new file mode 100644
index 0000000..0e53bfa
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginForm.design
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/loginForm/loginFormController.js b/examples/main/default/aura/loginForm/loginFormController.js
new file mode 100644
index 0000000..d37160e
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginFormController.js
@@ -0,0 +1,70 @@
+({
+ initialize: function(component, event, helper) {
+ $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire();
+ $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap2}).fire();
+ component.set('v.isUsernamePasswordEnabled', helper.getIsUsernamePasswordEnabled(component, event, helper));
+ component.set("v.isSelfRegistrationEnabled", helper.getIsSelfRegistrationEnabled(component, event, helper));
+ component.set("v.communityForgotPasswordUrl", helper.getCommunityForgotPasswordUrl(component, event, helper));
+ component.set("v.communitySelfRegisterUrl", helper.getCommunitySelfRegisterUrl(component, event, helper));
+ },
+
+ handleLogin: function (component, event, helpler) {
+ helpler.handleLogin(component, event, helpler);
+ },
+
+ setStartUrl: function (component, event, helpler) {
+ var startUrl = event.getParam('startURL');
+ if(startUrl) {
+ component.set("v.startUrl", startUrl);
+ }
+ },
+
+ setExpId: function (component, event, helper) {
+ var expId = event.getParam('expid');
+ if (expId) {
+ component.set("v.expid", expId);
+ }
+ helper.setBrandingCookie(component, event, helper);
+ },
+
+ onKeyUp: function(component, event, helpler){
+ //checks for "enter" key
+ if (event.getParam('keyCode')===13) {
+ helpler.handleLogin(component, event, helpler);
+ }
+ },
+
+ navigateToForgotPassword: function(cmp, event, helper) {
+ var forgotPwdUrl = cmp.get("v.communityForgotPasswordUrl");
+ if ($A.util.isUndefinedOrNull(forgotPwdUrl)) {
+ forgotPwdUrl = cmp.get("v.forgotPasswordUrl");
+ }
+ var startUrl = cmp.get("v.startUrl");
+ if(startUrl){
+ if(forgotPwdUrl.indexOf("?") === -1) {
+ forgotPwdUrl = forgotPwdUrl + '?startURL=' + decodeURIComponent(startUrl);
+ } else {
+ forgotPwdUrl = forgotPwdUrl + '&startURL=' + decodeURIComponent(startUrl);
+ }
+ }
+ var attributes = { url: forgotPwdUrl };
+ $A.get("e.force:navigateToURL").setParams(attributes).fire();
+ },
+
+ navigateToSelfRegister: function(cmp, event, helper) {
+ var selfRegUrl = cmp.get("v.communitySelfRegisterUrl");
+ if (selfRegUrl == null) {
+ selfRegUrl = cmp.get("v.selfRegisterUrl");
+ }
+ var startUrl = cmp.get("v.startUrl");
+ if(startUrl){
+ if(selfRegUrl.indexOf("?") === -1) {
+ selfRegUrl = selfRegUrl + '?startURL=' + decodeURIComponent(startUrl);
+ } else {
+ selfRegUrl = selfRegUrl + '&startURL=' + decodeURIComponent(startUrl);
+ }
+ }
+ var attributes = { url: selfRegUrl };
+ $A.get("e.force:navigateToURL").setParams(attributes).fire();
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/loginForm/loginFormHelper.js b/examples/main/default/aura/loginForm/loginFormHelper.js
new file mode 100644
index 0000000..5ec671c
--- /dev/null
+++ b/examples/main/default/aura/loginForm/loginFormHelper.js
@@ -0,0 +1,83 @@
+({
+
+ qsToEventMap: {
+ 'startURL' : 'e.c:setStartUrl'
+ },
+
+ qsToEventMap2: {
+ 'expid' : 'e.c:setExpId'
+ },
+
+ handleLogin: function (component, event, helpler) {
+ var username = component.find("username").get("v.value");
+ var password = component.find("password").get("v.value");
+ var action = component.get("c.login");
+ var startUrl = component.get("v.startUrl");
+
+ startUrl = decodeURIComponent(startUrl);
+
+ action.setParams({username:username, password:password, startUrl:startUrl});
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set("v.errorMessage",rtnValue);
+ component.set("v.showError",true);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ getIsUsernamePasswordEnabled : function (component, event, helpler) {
+ var action = component.get("c.getIsUsernamePasswordEnabled");
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set('v.isUsernamePasswordEnabled',rtnValue);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ getIsSelfRegistrationEnabled : function (component, event, helpler) {
+ var action = component.get("c.getIsSelfRegistrationEnabled");
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set('v.isSelfRegistrationEnabled',rtnValue);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ getCommunityForgotPasswordUrl : function (component, event, helpler) {
+ var action = component.get("c.getForgotPasswordUrl");
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set('v.communityForgotPasswordUrl',rtnValue);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ getCommunitySelfRegisterUrl : function (component, event, helpler) {
+ var action = component.get("c.getSelfRegistrationUrl");
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set('v.communitySelfRegisterUrl',rtnValue);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ setBrandingCookie: function (component, event, helpler) {
+ var expId = component.get("v.expid");
+ if (expId) {
+ var action = component.get("c.setExperienceId");
+ action.setParams({expId:expId});
+ action.setCallback(this, function(a){ });
+ $A.enqueueAction(action);
+ }
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/selfRegister/selfRegister.cmp b/examples/main/default/aura/selfRegister/selfRegister.cmp
new file mode 100644
index 0000000..5045e0d
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegister.cmp
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml b/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml
new file mode 100644
index 0000000..b9d5fb8
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegister.cmp-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Sample Component for selfRegister
+
diff --git a/examples/main/default/aura/selfRegister/selfRegister.css b/examples/main/default/aura/selfRegister/selfRegister.css
new file mode 100644
index 0000000..2d45808
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegister.css
@@ -0,0 +1,174 @@
+.THIS #sfdc_username_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_nickname_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_email_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_extrafield_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_user{
+ float:left;
+ width:23px;
+ height:25px;
+ padding-top:1px;
+ padding-left:2px;
+ margin:0px;
+
+}
+
+.THIS #sfdc_lock{
+ float:left;
+ width:23px;
+ height:25px;
+ padding-top:1px;
+ padding-left:2px;
+ margin:0px;
+}
+
+.THIS .login-icon {
+ color:#ccc;font-size:22px;
+}
+
+.THIS #sfdc_password_container{
+ margin-bottom:10px;
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS #sfdc_confirm_password_container{
+ padding: 12px;
+ background-color:white;
+ border: 1px solid #CCC;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.THIS button.sfdc_button {
+ width: 100%;
+ margin-top: 15px;
+ margin-bottom: 5px;
+ color: #fff;
+ background-color: #0070d2;
+ border-color: #357ebd;
+ display: inline-block;
+ text-align: center;
+ vertical-align: middle;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 10px 12px;
+ font-size: 16px;
+ font-family: 'Open Sans', sans-serif;
+ font-weight: 300;
+ line-height: 1.42857143;
+ border-radius: 2px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+.THIS button:hover {
+ background-color:#3276b1;
+ border-color:#285e8e;
+ cursor:pointer;
+}
+
+.THIS input {
+ margin-left:10px;
+ margin-top: 3px;
+ border: 0px solid transparent;
+ width: 70%;
+ -webkit-appearance: none;
+ font-size: 14px;
+}
+
+.THIS #sfdc_forgot{
+ font-family: 'Open Sans', 'sans-serif';
+ font-weight:300;
+ font-size: 14px;
+ margin-top: 10px
+}
+
+.THIS #error {
+ text-align: center;
+ color:#FF0000;
+}
+
+.THIS a {
+ color:white;
+ text-decoration: none;
+}
+.THIS a:hover {
+ color:white;
+ text-decoration: none;
+}
+
+.THIS input[type="checkbox"] {
+ appearance: none;
+ border: 1px solid white;
+ height: 22px;
+ width: 22px;
+ vertical-align: middle;
+}
+
+.THIS input[type="checkbox"]:checked {
+ border: 1px solid ;
+}
+
+.THIS input[type="checkbox"]:checked:after {
+ display: block;
+ position: relative;
+ content: '';
+ left: 3px;
+ top: 3px;
+ height: 6px;
+ width: 10px;
+ border-bottom: 4px solid /*#354452*/ white;
+ border-left: 4px solid /*#354452*/ white;
+ transform:rotate(-45deg);
+}
+
+.THIS input[type="checkbox"].disabled {
+ opacity: 0.35;
+}
+
+.THIS input[type="checkbox"] + label {
+ vertical-align: middle;
+}
\ No newline at end of file
diff --git a/examples/main/default/aura/selfRegister/selfRegister.design b/examples/main/default/aura/selfRegister/selfRegister.design
new file mode 100644
index 0000000..48c2743
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegister.design
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/selfRegister/selfRegisterController.js b/examples/main/default/aura/selfRegister/selfRegisterController.js
new file mode 100644
index 0000000..6981bb6
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegisterController.js
@@ -0,0 +1,33 @@
+({
+ initialize: function(component, event, helper) {
+ $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap}).fire();
+ $A.get("e.siteforce:registerQueryEventMap").setParams({"qsToEvent" : helper.qsToEventMap2}).fire();
+ component.set('v.extraFields', helper.getExtraFields(component, event, helper));
+ },
+
+ handleSelfRegister: function (component, event, helpler) {
+ helpler.handleSelfRegister(component, event, helpler);
+ },
+
+ setStartUrl: function (component, event, helpler) {
+ var startUrl = event.getParam('startURL');
+ if(startUrl) {
+ component.set("v.startUrl", startUrl);
+ }
+ },
+
+ setExpId: function (component, event, helper) {
+ var expId = event.getParam('expid');
+ if (expId) {
+ component.set("v.expid", expId);
+ }
+ helper.setBrandingCookie(component, event, helper);
+ },
+
+ onKeyUp: function(component, event, helpler){
+ //checks for "enter" key
+ if (event.getParam('keyCode')===13) {
+ helpler.handleSelfRegister(component, event, helpler);
+ }
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/selfRegister/selfRegisterHelper.js b/examples/main/default/aura/selfRegister/selfRegisterHelper.js
new file mode 100644
index 0000000..f12dd70
--- /dev/null
+++ b/examples/main/default/aura/selfRegister/selfRegisterHelper.js
@@ -0,0 +1,58 @@
+({
+ qsToEventMap: {
+ 'startURL' : 'e.c:setStartUrl'
+ },
+
+ qsToEventMap2: {
+ 'expid' : 'e.c:setExpId'
+ },
+
+ handleSelfRegister: function (component, event, helpler) {
+ var accountId = component.get("v.accountId");
+ var regConfirmUrl = component.get("v.regConfirmUrl");
+ var firstname = component.find("firstname").get("v.value");
+ var lastname = component.find("lastname").get("v.value");
+ var email = component.find("email").get("v.value");
+ var includePassword = component.get("v.includePasswordField");
+ var password = component.find("password").get("v.value");
+ var confirmPassword = component.find("confirmPassword").get("v.value");
+ var action = component.get("c.selfRegister");
+ var extraFields = JSON.stringify(component.get("v.extraFields")); // somehow apex controllers refuse to deal with list of maps
+ var startUrl = component.get("v.startUrl");
+
+ startUrl = decodeURIComponent(startUrl);
+
+ action.setParams({firstname:firstname,lastname:lastname,email:email,
+ password:password, confirmPassword:confirmPassword, accountId:accountId, regConfirmUrl:regConfirmUrl, extraFields:extraFields, startUrl:startUrl, includePassword:includePassword});
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set("v.errorMessage",rtnValue);
+ component.set("v.showError",true);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ getExtraFields : function (component, event, helpler) {
+ var action = component.get("c.getExtraFields");
+ action.setParam("extraFieldsFieldSet", component.get("v.extraFieldsFieldSet"));
+ action.setCallback(this, function(a){
+ var rtnValue = a.getReturnValue();
+ if (rtnValue !== null) {
+ component.set('v.extraFields',rtnValue);
+ }
+ });
+ $A.enqueueAction(action);
+ },
+
+ setBrandingCookie: function (component, event, helpler) {
+ var expId = component.get("v.expid");
+ if (expId) {
+ var action = component.get("c.setExperienceId");
+ action.setParams({expId:expId});
+ action.setCallback(this, function(a){ });
+ $A.enqueueAction(action);
+ }
+ }
+})
\ No newline at end of file
diff --git a/examples/main/default/aura/setExpId/setExpId.evt b/examples/main/default/aura/setExpId/setExpId.evt
new file mode 100644
index 0000000..c1badd2
--- /dev/null
+++ b/examples/main/default/aura/setExpId/setExpId.evt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/setExpId/setExpId.evt-meta.xml b/examples/main/default/aura/setExpId/setExpId.evt-meta.xml
new file mode 100644
index 0000000..515845f
--- /dev/null
+++ b/examples/main/default/aura/setExpId/setExpId.evt-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Sample Event for setExpId
+
diff --git a/examples/main/default/aura/setStartUrl/setStartUrl.evt b/examples/main/default/aura/setStartUrl/setStartUrl.evt
new file mode 100644
index 0000000..7b0813b
--- /dev/null
+++ b/examples/main/default/aura/setStartUrl/setStartUrl.evt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml b/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml
new file mode 100644
index 0000000..e35c920
--- /dev/null
+++ b/examples/main/default/aura/setStartUrl/setStartUrl.evt-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Sample Event for setStartUrl
+
diff --git a/examples/main/default/classes/ChangePasswordController.cls b/examples/main/default/classes/ChangePasswordController.cls
new file mode 100644
index 0000000..31dde01
--- /dev/null
+++ b/examples/main/default/classes/ChangePasswordController.cls
@@ -0,0 +1,14 @@
+/**
+ * An apex page controller that exposes the change password functionality
+ */
+public with sharing class ChangePasswordController {
+ public String oldPassword {get; set;}
+ public String newPassword {get; set;}
+ public String verifyNewPassword {get; set;}
+
+ public PageReference changePassword() {
+ return Site.changePassword(newPassword, verifyNewPassword, oldpassword);
+ }
+
+ public ChangePasswordController() {}
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/ChangePasswordController.cls-meta.xml b/examples/main/default/classes/ChangePasswordController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/ChangePasswordController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/ChangePasswordControllerTest.cls b/examples/main/default/classes/ChangePasswordControllerTest.cls
new file mode 100644
index 0000000..2814e42
--- /dev/null
+++ b/examples/main/default/classes/ChangePasswordControllerTest.cls
@@ -0,0 +1,14 @@
+/**
+ * An apex page controller that exposes the change password functionality
+ */
+@IsTest public with sharing class ChangePasswordControllerTest {
+ @IsTest(SeeAllData=true) public static void testChangePasswordController() {
+ // Instantiate a new controller with all parameters in the page
+ ChangePasswordController controller = new ChangePasswordController();
+ controller.oldPassword = '123456';
+ controller.newPassword = 'qwerty1';
+ controller.verifyNewPassword = 'qwerty1';
+
+ System.assertEquals(controller.changePassword(),null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml b/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/ChangePasswordControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesLandingController.cls b/examples/main/default/classes/CommunitiesLandingController.cls
new file mode 100644
index 0000000..9633c5a
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLandingController.cls
@@ -0,0 +1,12 @@
+/**
+ * An apex page controller that takes the user to the right start page based on credentials or lack thereof
+ */
+public with sharing class CommunitiesLandingController {
+
+ // Code we will invoke on page load.
+ public PageReference forwardToStartPage() {
+ return Network.communitiesLanding();
+ }
+
+ public CommunitiesLandingController() {}
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml b/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLandingController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesLandingControllerTest.cls b/examples/main/default/classes/CommunitiesLandingControllerTest.cls
new file mode 100644
index 0000000..84b5643
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLandingControllerTest.cls
@@ -0,0 +1,18 @@
+/**
+ * An apex page controller that takes the user to the right start page based on credentials or lack thereof
+ */
+@IsTest public with sharing class CommunitiesLandingControllerTest {
+ @IsTest(SeeAllData=true) public static void testCommunitiesLandingController() {
+ // Instantiate a new controller with all parameters in the page
+ CommunitiesLandingController controller = new CommunitiesLandingController();
+ PageReference pageRef = controller.forwardToStartPage();
+ //PageRef is either null or an empty object in test context
+ if(pageRef != null){
+ String url = pageRef.getUrl();
+ if(url != null){
+ System.assertEquals(true, String.isEmpty(url));
+ //show up in perforce
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLandingControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesLoginController.cls b/examples/main/default/classes/CommunitiesLoginController.cls
new file mode 100644
index 0000000..35ab098
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLoginController.cls
@@ -0,0 +1,14 @@
+/**
+ * An apex page controller that exposes the site login functionality
+ */
+global with sharing class CommunitiesLoginController {
+
+ global CommunitiesLoginController () {}
+
+ // Code we will invoke on page load.
+ global PageReference forwardToAuthPage() {
+ String startUrl = System.currentPageReference().getParameters().get('startURL');
+ String displayType = System.currentPageReference().getParameters().get('display');
+ return Network.forwardToAuthPage(startUrl, displayType);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml b/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLoginController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesLoginControllerTest.cls b/examples/main/default/classes/CommunitiesLoginControllerTest.cls
new file mode 100644
index 0000000..b892fdc
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLoginControllerTest.cls
@@ -0,0 +1,10 @@
+/**
+ * An apex page controller that exposes the site login functionality
+ */
+@IsTest global with sharing class CommunitiesLoginControllerTest {
+ @IsTest(SeeAllData=true)
+ global static void testCommunitiesLoginController () {
+ CommunitiesLoginController controller = new CommunitiesLoginController();
+ System.assertEquals(null, controller.forwardToAuthPage());
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesLoginControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls
new file mode 100644
index 0000000..d98d1fe
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls
@@ -0,0 +1,7 @@
+/**
+ * An apex page controller that takes the user to the right start page based on credentials or lack thereof
+ */
+public with sharing class CommunitiesSelfRegConfirmController {
+
+ public CommunitiesSelfRegConfirmController() {}
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegConfirmController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls
new file mode 100644
index 0000000..18a60bb
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls
@@ -0,0 +1,9 @@
+/**
+ * An apex page controller that takes the user to the right start page based on credentials or lack thereof
+ */
+@IsTest public with sharing class CommunitiesSelfRegConfirmControllerTest {
+ @IsTest(SeeAllData=true) public static void testCommunitiesSelfRegConfirmController() {
+ // Instantiate a new controller with all parameters in the page
+ CommunitiesSelfRegConfirmController controller = new CommunitiesSelfRegConfirmController();
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegConfirmControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesSelfRegController.cls b/examples/main/default/classes/CommunitiesSelfRegController.cls
new file mode 100644
index 0000000..3b738aa
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegController.cls
@@ -0,0 +1,74 @@
+/**
+ * An apex page controller that supports self registration of users in communities that allow self registration
+ */
+public class CommunitiesSelfRegController {
+
+ public String firstName {get; set;}
+ public String lastName {get; set;}
+ public String email {get; set;}
+ public String password {get; set {password = value == null ? value : value.trim(); } }
+ public String confirmPassword {get; set { confirmPassword = value == null ? value : value.trim(); } }
+ public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } }
+
+ public CommunitiesSelfRegController() {
+ String expid = ApexPages.currentPage().getParameters().get('expid');
+ if (expId != null) {
+ Site.setExperienceId(expId);
+ }
+ }
+
+ private boolean isValidPassword() {
+ return password == confirmPassword;
+ }
+
+ public PageReference registerUser() {
+
+ // it's okay if password is null - we'll send the user a random password in that case
+ if (!isValidPassword()) {
+ ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match);
+ ApexPages.addMessage(msg);
+ return null;
+ }
+
+ String profileId = null; // To be filled in by customer.
+ String roleEnum = null; // To be filled in by customer.
+ String accountId = ''; // To be filled in by customer.
+
+ String userName = email;
+
+ User u = new User();
+ u.Username = userName;
+ u.Email = email;
+ u.FirstName = firstName;
+ u.LastName = lastName;
+ u.CommunityNickname = communityNickname;
+ u.ProfileId = profileId;
+
+ String userId;
+
+ try {
+ userId = Site.createExternalUser(u, accountId, password);
+ } catch(Site.ExternalUserCreateException ex) {
+ List errors = ex.getDisplayMessages();
+ for (String error : errors) {
+ ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error));
+ }
+
+ // This message is used for debugging. Do not display this in the UI to the end user.
+ // It has the information around why the user creation failed.
+ System.debug(ex.getMessage());
+ }
+
+ if (userId != null) {
+ if (password != null && password.length() > 1) {
+ return Site.login(userName, password, ApexPages.currentPage().getParameters().get('startURL'));
+ }
+ else {
+ PageReference page = System.Page.CommunitiesSelfRegConfirm;
+ page.setRedirect(true);
+ return page;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls
new file mode 100644
index 0000000..bda9c05
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls
@@ -0,0 +1,20 @@
+/**
+ * An apex page controller that supports self registration of users in communities that allow self registration
+ */
+@IsTest public with sharing class CommunitiesSelfRegControllerTest {
+ @IsTest(SeeAllData=true)
+ public static void testCommunitiesSelfRegController() {
+ CommunitiesSelfRegController controller = new CommunitiesSelfRegController();
+ controller.firstName = 'FirstName';
+ controller.lastName = 'LastName';
+ controller.email = 'test@force.com';
+ controller.communityNickname = 'test';
+
+ // registerUser will always return null when the page isn't accessed as a guest user
+ System.assert(controller.registerUser() == null);
+
+ controller.password = 'abcd1234';
+ controller.confirmPassword = 'abcd123';
+ System.assert(controller.registerUser() == null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/CommunitiesSelfRegControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/ForgotPasswordController.cls b/examples/main/default/classes/ForgotPasswordController.cls
new file mode 100644
index 0000000..480df8b
--- /dev/null
+++ b/examples/main/default/classes/ForgotPasswordController.cls
@@ -0,0 +1,19 @@
+/**
+ * An apex page controller that exposes the site forgot password functionality
+ */
+public with sharing class ForgotPasswordController {
+ public String username {get; set;}
+
+ public ForgotPasswordController() {}
+
+ public PageReference forgotPassword() {
+ boolean success = Site.forgotPassword(username);
+ PageReference pr = Page.ForgotPasswordConfirm;
+ pr.setRedirect(true);
+
+ if (success) {
+ return pr;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/ForgotPasswordController.cls-meta.xml b/examples/main/default/classes/ForgotPasswordController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/ForgotPasswordController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/ForgotPasswordControllerTest.cls b/examples/main/default/classes/ForgotPasswordControllerTest.cls
new file mode 100644
index 0000000..1712f2c
--- /dev/null
+++ b/examples/main/default/classes/ForgotPasswordControllerTest.cls
@@ -0,0 +1,12 @@
+/**
+ * An apex page controller that exposes the site forgot password functionality
+ */
+@IsTest public with sharing class ForgotPasswordControllerTest {
+ @IsTest(SeeAllData=true) public static void testForgotPasswordController() {
+ // Instantiate a new controller with all parameters in the page
+ ForgotPasswordController controller = new ForgotPasswordController();
+ controller.username = 'test@salesforce.com';
+
+ System.assertEquals(controller.forgotPassword(),null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml b/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/ForgotPasswordControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningForgotPasswordController.cls b/examples/main/default/classes/LightningForgotPasswordController.cls
new file mode 100644
index 0000000..2a21187
--- /dev/null
+++ b/examples/main/default/classes/LightningForgotPasswordController.cls
@@ -0,0 +1,35 @@
+global class LightningForgotPasswordController {
+
+ public LightningForgotPasswordController() {
+
+ }
+
+ @AuraEnabled
+ public static String forgotPassword(String username, String checkEmailUrl) {
+ try {
+ Site.forgotPassword(username);
+ ApexPages.PageReference checkEmailRef = new PageReference(checkEmailUrl);
+ if(!Site.isValidUsername(username)) {
+ return Label.Site.invalid_email;
+ }
+ aura.redirect(checkEmailRef);
+ return null;
+ }
+ catch (Exception ex) {
+ return ex.getMessage();
+ }
+ }
+
+ @AuraEnabled
+ global static String setExperienceId(String expId) {
+ // Return null if there is no error, else it will return the error message
+ try {
+ if (expId != null) {
+ Site.setExperienceId(expId);
+ }
+ return null;
+ } catch (Exception ex) {
+ return ex.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml b/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningForgotPasswordController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningForgotPasswordControllerTest.cls b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls
new file mode 100644
index 0000000..ab49bb8
--- /dev/null
+++ b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls
@@ -0,0 +1,25 @@
+@IsTest(SeeAllData = true)
+public with sharing class LightningForgotPasswordControllerTest {
+
+ /* Verifies that ForgotPasswordController handles invalid usernames appropriately */
+ @IsTest
+ static void testLightningForgotPasswordControllerInvalidUserName() {
+ System.assertEquals(LightningForgotPasswordController.forgotPassword('fakeUser', 'http://a.com'), Label.Site.invalid_email);
+ System.assertEquals(LightningForgotPasswordController.forgotPassword(null, 'http://a.com'), Label.Site.invalid_email);
+ System.assertEquals(LightningForgotPasswordController.forgotPassword('a', '/home/home.jsp'), Label.Site.invalid_email);
+ }
+
+ /* Verifies that null checkEmailRef url throws proper exception. */
+ @IsTest
+ static void testLightningForgotPasswordControllerWithNullCheckEmailRef() {
+ System.assertEquals(LightningForgotPasswordController.forgotPassword('a', null), 'Argument 1 cannot be null');
+ System.assertEquals(LightningForgotPasswordController.forgotPassword('a@salesforce.com', null), 'Argument 1 cannot be null');
+ }
+
+ /* Verifies that LightningForgotPasswordController object is instantiated correctly. */
+ @IsTest
+ static void LightningForgotPasswordControllerInstantiation() {
+ LightningForgotPasswordController controller = new LightningForgotPasswordController();
+ System.assertNotEquals(controller, null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningForgotPasswordControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningLoginFormController.cls b/examples/main/default/classes/LightningLoginFormController.cls
new file mode 100644
index 0000000..3c77267
--- /dev/null
+++ b/examples/main/default/classes/LightningLoginFormController.cls
@@ -0,0 +1,65 @@
+global class LightningLoginFormController {
+
+ public LightningLoginFormController() {
+
+ }
+
+ @AuraEnabled
+ public static String login(String username, String password, String startUrl) {
+ try{
+ ApexPages.PageReference lgn = Site.login(username, password, startUrl);
+ aura.redirect(lgn);
+ return null;
+ }
+ catch (Exception ex) {
+ return ex.getMessage();
+ }
+ }
+
+ @AuraEnabled
+ public static Boolean getIsUsernamePasswordEnabled() {
+ Auth.AuthConfiguration authConfig = getAuthConfig();
+ return authConfig.getUsernamePasswordEnabled();
+ }
+
+ @AuraEnabled
+ public static Boolean getIsSelfRegistrationEnabled() {
+ Auth.AuthConfiguration authConfig = getAuthConfig();
+ return authConfig.getSelfRegistrationEnabled();
+ }
+
+ @AuraEnabled
+ public static String getSelfRegistrationUrl() {
+ Auth.AuthConfiguration authConfig = getAuthConfig();
+ if (authConfig.getSelfRegistrationEnabled()) {
+ return authConfig.getSelfRegistrationUrl();
+ }
+ return null;
+ }
+
+ @AuraEnabled
+ public static String getForgotPasswordUrl() {
+ Auth.AuthConfiguration authConfig = getAuthConfig();
+ return authConfig.getForgotPasswordUrl();
+ }
+
+ @TestVisible
+ private static Auth.AuthConfiguration getAuthConfig(){
+ Id networkId = Network.getNetworkId();
+ Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(networkId,'');
+ return authConfig;
+ }
+
+ @AuraEnabled
+ global static String setExperienceId(String expId) {
+ // Return null if there is no error, else it will return the error message
+ try {
+ if (expId != null) {
+ Site.setExperienceId(expId);
+ }
+ return null;
+ } catch (Exception ex) {
+ return ex.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningLoginFormController.cls-meta.xml b/examples/main/default/classes/LightningLoginFormController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningLoginFormController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningLoginFormControllerTest.cls b/examples/main/default/classes/LightningLoginFormControllerTest.cls
new file mode 100644
index 0000000..b008372
--- /dev/null
+++ b/examples/main/default/classes/LightningLoginFormControllerTest.cls
@@ -0,0 +1,30 @@
+@IsTest(SeeAllData = true)
+public with sharing class LightningLoginFormControllerTest {
+
+ @IsTest
+ static void LightningLoginFormControllerInstantiation() {
+ LightningLoginFormController controller = new LightningLoginFormController();
+ System.assertNotEquals(controller, null);
+ }
+
+ @IsTest
+ static void testIsUsernamePasswordEnabled() {
+ System.assertEquals(true, LightningLoginFormController.getIsUsernamePasswordEnabled());
+ }
+
+ @IsTest
+ static void testIsSelfRegistrationEnabled() {
+ System.assertEquals(false, LightningLoginFormController.getIsSelfRegistrationEnabled());
+ }
+
+ @IsTest
+ static void testGetSelfRegistrationURL() {
+ System.assertEquals(null, LightningLoginFormController.getSelfRegistrationUrl());
+ }
+
+ @IsTest
+ static void testAuthConfig() {
+ Auth.AuthConfiguration authConfig = LightningLoginFormController.getAuthConfig();
+ System.assertNotEquals(null, authConfig);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml b/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningLoginFormControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningSelfRegisterController.cls b/examples/main/default/classes/LightningSelfRegisterController.cls
new file mode 100644
index 0000000..b284918
--- /dev/null
+++ b/examples/main/default/classes/LightningSelfRegisterController.cls
@@ -0,0 +1,137 @@
+global class LightningSelfRegisterController {
+
+ public LightningSelfRegisterController() {
+
+ }
+
+ @TestVisible
+ private static boolean isValidPassword(String password, String confirmPassword) {
+ return password == confirmPassword;
+ }
+
+ @TestVisible
+ private static boolean siteAsContainerEnabled(String communityUrl) {
+ Auth.AuthConfiguration authConfig = new Auth.AuthConfiguration(communityUrl,'');
+ return authConfig.isCommunityUsingSiteAsContainer();
+ }
+
+ @TestVisible
+ private static void validatePassword(User u, String password, String confirmPassword) {
+ if(!Test.isRunningTest()) {
+ Site.validatePassword(u, password, confirmPassword);
+ }
+ return;
+ }
+
+ @AuraEnabled
+ public static String selfRegister(String firstname ,String lastname, String email, String password, String confirmPassword, String accountId, String regConfirmUrl, String extraFields, String startUrl, Boolean includePassword) {
+ Savepoint sp = null;
+ try {
+ sp = Database.setSavepoint();
+
+ if (lastname == null || String.isEmpty(lastname)) {
+ return Label.Site.lastname_is_required;
+ }
+
+ if (email == null || String.isEmpty(email)) {
+ return Label.Site.email_is_required;
+ }
+
+ User u = new User();
+ u.Username = email;
+ u.put('Email',email);
+
+ u.FirstName = firstname;
+ u.LastName = lastname;
+
+ String networkId = Network.getNetworkId();
+
+ // If using site to host the community the user should not hit s1 after logging in from mobile.
+ if(networkId != null && siteAsContainerEnabled(Network.getLoginUrl(networkId))) {
+ u.put('UserPreferencesHideS1BrowserUI',true);
+ }
+
+ String nickname = ((firstname != null && firstname.length() > 0) ? firstname.substring(0,1) : '' ) + lastname.substring(0,1);
+ nickname += String.valueOf(Crypto.getRandomInteger()).substring(1,7);
+ u.put('CommunityNickname', nickname);
+
+ if (extraFields != null) {
+ List extraFieldsList = (List) JSON.deserializeUntyped(extraFields);
+ for (Object thisFieldObject : extraFieldsList) {
+ Map thisField = (Map) thisFieldObject;
+ Schema.SObjectField sof = Schema.SObjectType.User.fields.getMap().get((String) thisField.get('fieldPath'));
+ u.put(sof, thisField.get('value'));
+ }
+ }
+
+ if (includePassword) {
+ if (!isValidPassword(password, confirmPassword)) {
+ return Label.site.passwords_dont_match;
+ }
+ validatePassword(u, password, confirmPassword);
+ }
+ else {
+ password = null;
+ }
+
+ // lastName is a required field on user, but if it isn't specified, we'll default it to the username
+ String userId = Site.createPortalUser(u, accountId, password);
+ // create a fake userId for test.
+ if (Test.isRunningTest()) {
+ userId = 'fakeUserId';
+ }
+ if (userId != null) {
+ if (password != null && password.length() > 1) {
+ ApexPages.PageReference lgn = Site.login(email, password, startUrl);
+ if(!Test.isRunningTest()) {
+ aura.redirect(lgn);
+ }
+ }
+ else {
+ ApexPages.PageReference confirmRef = new PageReference(regConfirmUrl);
+ if(!Test.isRunningTest()) {
+ aura.redirect(confirmRef);
+ }
+
+ }
+ }
+ return null;
+ }
+ catch (Exception ex) {
+ Database.rollback(sp);
+ return ex.getMessage();
+ }
+ }
+
+ @AuraEnabled
+ public static List> getExtraFields(String extraFieldsFieldSet) {
+ List> extraFields = new List>();
+ Schema.FieldSet fieldSet = Schema.SObjectType.User.fieldSets.getMap().get(extraFieldsFieldSet);
+ if(!Test.isRunningTest()) {
+ if (fieldSet != null) {
+ for (Schema.FieldSetMember f : fieldSet.getFields()) {
+ Map fieldDetail = new Map();
+ fieldDetail.put('dbRequired', f.getDBRequired());
+ fieldDetail.put('fieldPath', f.getFieldPath());
+ fieldDetail.put('label', f.getLabel());
+ fieldDetail.put('required', f.getRequired());
+ fieldDetail.put('type', f.getType());
+ fieldDetail.put('value', ''); // client will populate
+ extraFields.add(fieldDetail);
+ }}}
+ return extraFields;
+ }
+
+ @AuraEnabled
+ global static String setExperienceId(String expId) {
+ // Return null if there is no error, else it will return the error message
+ try {
+ if (expId != null) {
+ Site.setExperienceId(expId);
+ }
+ return null;
+ } catch (Exception ex) {
+ return ex.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml b/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningSelfRegisterController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/LightningSelfRegisterControllerTest.cls b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls
new file mode 100644
index 0000000..5dec8d9
--- /dev/null
+++ b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls
@@ -0,0 +1,102 @@
+@IsTest(SeeAllData = true)
+public with sharing class LightningSelfRegisterControllerTest {
+
+ /* Verifies that IsValidPassword method with various password combinations. */
+ @IsTest
+ static void testIsValidPassword() {
+ System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', 'password?@12334') == true);
+ System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', 'dummyPassword') == false);
+ System.assert(LightningSelfRegisterController.isValidPassword('password?@12334', null) == false);
+ System.assert(LightningSelfRegisterController.isValidPassword(null, 'fakePwd') == false);
+ }
+
+ @IsTest
+ static void testSiteAsContainerEnabled() {
+ System.assertNotEquals(null, LightningSelfRegisterController.siteAsContainerEnabled('https://portaleu1-developer-edition.eu11.force.com'));
+ }
+
+ /* Verifies the selfRegistration method flow with various invalid inputs */
+ @IsTest
+ static void testSelfRegistration() {
+ Map < String, String > paramsMap = initializeParams();
+ System.assertNotEquals(null, paramsMap);
+ System.assertEquals(Label.Site.lastname_is_required, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), '', paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true));
+ System.assertEquals(Label.Site.email_is_required, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), '', paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true));
+ System.assertEquals(Label.Site.email_is_required, LightningSelfRegisterController.selfRegister(null, paramsMap.get('lastName'), '', null, paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true));
+ System.assertEquals(Label.site.passwords_dont_match, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordWrong'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true));
+ System.assertNotEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), '', paramsMap.get('password'), paramsMap.get('confirmPasswordWrong'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), false));
+ }
+
+
+ /* Verifies the selfRegistration flow for valid inputs */
+ @IsTest
+ static void testSelfRegisterWithProperCredentials() {
+ Map < String, String > paramsMap = initializeParams();
+ System.assertEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), true));
+ }
+
+ /* Verifies SelfRegistration flow with an accounId that is created within the test */
+ @IsTest
+ static void testSelfRegisterWithCreatedAccount() {
+ Account acc = new Account(name = 'test acc');
+ insert acc;
+ List < Account > accounts = [SELECT Id FROM Account LIMIT 1];
+ System.assert(!accounts.isEmpty(), 'There must be at least one account in this environment!');
+ String accountId = accounts[0].Id;
+ Map < String, String > paramsMap = initializeParams();
+ System.assertEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), accountId, paramsMap.get('regConfirmUrl'), null, paramsMap.get('startUrl'), false));
+ }
+
+ @IsTest
+ static void testGetNullExtraFields() {
+ System.assertEquals(new List < Map < String, Object >> (), LightningSelfRegisterController.getExtraFields(null));
+ }
+
+ @IsTest
+ static void testGetNonEmptyExtraFields() {
+ System.assertEquals(new List < Map < String, Object >> (), LightningSelfRegisterController.getExtraFields('field1'));
+ }
+
+ /* Verifies validation of extraFields within the Self Registration flow */
+ @IsTest
+ static void testGetExtraFieldsInSelfRegistration() {
+ List < Map < String, Object >> fieldlist = new List < Map < String, Object >> ();
+ Map < String, String > paramsMap = initializeParams();
+ Map < String, Object > fieldMap = new Map < String, Object > ();
+ fieldMap.put('description', 'new field');
+ fieldMap.put('fieldPath', 'dummyPath');
+ fieldlist.add(fieldMap);
+ String extraFields = JSON.serialize(fieldlist);
+ System.assertNotEquals(null, LightningSelfRegisterController.selfRegister(paramsMap.get('firstName'), paramsMap.get('lastName'), paramsMap.get('email'), paramsMap.get('password'), paramsMap.get('confirmPasswordCorrect'), null, paramsMap.get('regConfirmUrl'), extraFields, paramsMap.get('startUrl'), true));
+ }
+
+ @IsTest
+ static void LightningSelfRegisterControllerInstantiation() {
+ LightningSelfRegisterController controller = new LightningSelfRegisterController();
+ System.assertNotEquals(controller, null);
+ }
+
+ /* Helper method to initialize the parameters required for SelfRegistration. */
+ private static Map < String, String > initializeParams() {
+ Map < String, String > paramsMap = new Map < String, String > ();
+ String firstName = 'test';
+ String lastName = 'User';
+ String email = 'testUser@salesforce.com';
+ String password = 'testuser123';
+ String confirmPasswordCorrect = 'testuser123';
+ String confirmPasswordWrong = 'wrongpassword';
+ String accountId = 'testuser123';
+ String regConfirmUrl = 'http://registration-confirm.com';
+ String startUrl = 'http://my.company.salesforce.com';
+ paramsMap.put('firstName', firstName);
+ paramsMap.put('lastName', lastName);
+ paramsMap.put('email', email);
+ paramsMap.put('password', password);
+ paramsMap.put('confirmPasswordCorrect', confirmPasswordCorrect);
+ paramsMap.put('confirmPasswordWrong', confirmPasswordWrong);
+ paramsMap.put('accountId', accountId);
+ paramsMap.put('regConfirmUrl', regConfirmUrl);
+ paramsMap.put('startUrl', startUrl);
+ return paramsMap;
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/LightningSelfRegisterControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/MicrobatchSelfRegController.cls b/examples/main/default/classes/MicrobatchSelfRegController.cls
new file mode 100644
index 0000000..e848951
--- /dev/null
+++ b/examples/main/default/classes/MicrobatchSelfRegController.cls
@@ -0,0 +1,57 @@
+public class MicrobatchSelfRegController {
+ public String firstName {get; set;}
+ public String lastName {get; set;}
+ public String email {get; set;}
+ public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } }
+
+ public MicrobatchSelfRegController() {
+ String expid = ApexPages.currentPage().getParameters().get('expid');
+ if (expId != null) {
+ Site.setExperienceId(expId);
+ }
+ }
+
+ public PageReference registerUser() {
+ String userName = email;
+ String accountName; // to be filled by customer
+ String contactName; //to be filled by customer
+ String profileId = null; //to be filled by customer
+ String UUID;
+
+ User u = new User();
+ u.Username = userName;
+ u.Email = email;
+ u.FirstName = firstName;
+ u.LastName = lastName;
+ u.CommunityNickname = communityNickname;
+ u.ProfileId = profileId;
+ u.LocaleSidKey = 'en_US';
+ u.TimeZoneSidKey = 'GMT';
+ u.LanguageLocaleKey = 'en_US';
+ u.EmailEncodingKey = 'UTF-8';
+
+ Account acc = new Account();
+ acc.Name = 'Account for ' + lastName;
+ Contact c = new Contact();
+ c.lastName = lastName;
+
+ try {
+ UUID = Network.createExternalUserAsync(u, c,acc);
+ } catch(Site.ExternalUserCreateException ex) {
+ List errors = ex.getDisplayMessages();
+ for (String error : errors) {
+ ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error));
+ }
+
+ // This message is used for debugging. Do not display this in the UI to the end user.
+ // It has the information around why the user creation failed.
+ System.debug(ex.getMessage());
+ }
+ if (UUID != null) {
+ PageReference page = System.Page.CommunitiesSelfRegConfirm;
+ page.setRedirect(true);
+ return page;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml b/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/MicrobatchSelfRegController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls
new file mode 100644
index 0000000..b6b8365
--- /dev/null
+++ b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls
@@ -0,0 +1,14 @@
+@IsTest
+ public with sharing class MicrobatchSelfRegControllerTest {
+ @IsTest(SeeAllData=true)
+ public static void testMicrobatchSelfRegController() {
+ MicrobatchSelfRegController controller = new MicrobatchSelfRegController();
+ controller.firstName = 'FirstName';
+ controller.lastName = 'LastName';
+ controller.email = 'test@force.com';
+ controller.communityNickname = 'test';
+
+ // registerUser will always return null when the page isn't accessed as a guest user
+ System.assert(controller.registerUser() == null);
+ }
+ }
\ No newline at end of file
diff --git a/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/MicrobatchSelfRegControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/MyProfilePageController.cls b/examples/main/default/classes/MyProfilePageController.cls
new file mode 100644
index 0000000..f3fe255
--- /dev/null
+++ b/examples/main/default/classes/MyProfilePageController.cls
@@ -0,0 +1,53 @@
+/**
+ * An apex class that updates portal user details.
+ Guest users are never able to access this page.
+ */
+public with sharing class MyProfilePageController {
+
+ private User user;
+ private boolean isEdit = false;
+
+ public User getUser() {
+ return user;
+ }
+
+ public MyProfilePageController() {
+ user = [SELECT id, email, username, usertype, communitynickname, timezonesidkey, languagelocalekey, firstname, lastname, phone, title,
+ street, city, country, postalcode, state, localesidkey, mobilephone, extension, fax, contact.email
+ FROM User
+ WHERE id = :UserInfo.getUserId()];
+ // guest users should never be able to access this page
+ if (user.usertype == 'GUEST') {
+ throw new NoAccessException();
+ }
+ }
+
+ public Boolean getIsEdit() {
+ return isEdit;
+ }
+
+ public void edit() {
+ isEdit=true;
+ }
+
+ public void save() {
+ try {
+ update user;
+ isEdit=false;
+ } catch(DmlException e) {
+ ApexPages.addMessages(e);
+ }
+ }
+
+ public PageReference changePassword() {
+ return Page.ChangePassword;
+ }
+
+ public void cancel() {
+ isEdit=false;
+ user = [SELECT id, email, username, communitynickname, timezonesidkey, languagelocalekey, firstname, lastname, phone, title,
+ street, city, country, postalcode, state, localesidkey, mobilephone, extension, fax, contact.email
+ FROM User
+ WHERE id = :UserInfo.getUserId()];
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/MyProfilePageController.cls-meta.xml b/examples/main/default/classes/MyProfilePageController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/MyProfilePageController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/MyProfilePageControllerTest.cls b/examples/main/default/classes/MyProfilePageControllerTest.cls
new file mode 100644
index 0000000..1b59342
--- /dev/null
+++ b/examples/main/default/classes/MyProfilePageControllerTest.cls
@@ -0,0 +1,55 @@
+/**
+ * An apex class that updates details of a portal user.
+ Guest users are never able to access this page.
+ */
+@IsTest public with sharing class MyProfilePageControllerTest {
+
+ @IsTest(SeeAllData=true) static void testSave() {
+ // Modify the test to query for a portal user that exists in your org
+ List existingPortalUsers = [SELECT id, profileId, userRoleId FROM User WHERE UserRoleId <> null AND UserType='CustomerSuccess'];
+
+ if (existingPortalUsers.isEmpty()) {
+ User currentUser = [select id, title, firstname, lastname, email, phone, mobilephone, fax, street, city, state, postalcode, country
+ FROM User WHERE id =: UserInfo.getUserId()];
+ MyProfilePageController controller = new MyProfilePageController();
+ System.assertEquals(currentUser.Id, controller.getUser().Id, 'Did not successfully load the current user');
+ System.assert(controller.getIsEdit() == false, 'isEdit should default to false');
+ controller.edit();
+ System.assert(controller.getIsEdit() == true);
+ controller.cancel();
+ System.assert(controller.getIsEdit() == false);
+
+ System.assert(Page.ChangePassword.getUrl().equals(controller.changePassword().getUrl()));
+
+ String randFax = Math.rint(Math.random() * 1000) + '5551234';
+ controller.getUser().Fax = randFax;
+ controller.save();
+ System.assert(controller.getIsEdit() == false);
+
+ currentUser = [Select id, fax from User where id =: currentUser.Id];
+ System.assert(currentUser.fax == randFax);
+ } else {
+ User existingPortalUser = existingPortalUsers[0];
+ String randFax = Math.rint(Math.random() * 1000) + '5551234';
+
+ System.runAs(existingPortalUser) {
+ MyProfilePageController controller = new MyProfilePageController();
+ System.assertEquals(existingPortalUser.Id, controller.getUser().Id, 'Did not successfully load the current user');
+ System.assert(controller.getIsEdit() == false, 'isEdit should default to false');
+ controller.edit();
+ System.assert(controller.getIsEdit() == true);
+
+ controller.cancel();
+ System.assert(controller.getIsEdit() == false);
+
+ controller.getUser().Fax = randFax;
+ controller.save();
+ System.assert(controller.getIsEdit() == false);
+ }
+
+ // verify that the user was updated
+ existingPortalUser = [Select id, fax from User where id =: existingPortalUser.Id];
+ System.assert(existingPortalUser.fax == randFax);
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml b/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/MyProfilePageControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/SiteLoginController.cls b/examples/main/default/classes/SiteLoginController.cls
new file mode 100644
index 0000000..fb2f4c7
--- /dev/null
+++ b/examples/main/default/classes/SiteLoginController.cls
@@ -0,0 +1,14 @@
+/**
+ * An apex page controller that exposes the site login functionality
+ */
+global with sharing class SiteLoginController {
+ global String username {get; set;}
+ global String password {get; set;}
+
+ global PageReference login() {
+ String startUrl = System.currentPageReference().getParameters().get('startURL');
+ return Site.login(username, password, startUrl);
+ }
+
+ global SiteLoginController () {}
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/SiteLoginController.cls-meta.xml b/examples/main/default/classes/SiteLoginController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/SiteLoginController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/SiteLoginControllerTest.cls b/examples/main/default/classes/SiteLoginControllerTest.cls
new file mode 100644
index 0000000..88ec4a9
--- /dev/null
+++ b/examples/main/default/classes/SiteLoginControllerTest.cls
@@ -0,0 +1,13 @@
+/**
+ * An apex page controller that exposes the site login functionality
+ */
+@IsTest global with sharing class SiteLoginControllerTest {
+ @IsTest(SeeAllData=true) global static void testSiteLoginController () {
+ // Instantiate a new controller with all parameters in the page
+ SiteLoginController controller = new SiteLoginController ();
+ controller.username = 'test@salesforce.com';
+ controller.password = '123456';
+
+ System.assertEquals(controller.login(),null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml b/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/SiteLoginControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/SiteRegisterController.cls b/examples/main/default/classes/SiteRegisterController.cls
new file mode 100644
index 0000000..9332bc4
--- /dev/null
+++ b/examples/main/default/classes/SiteRegisterController.cls
@@ -0,0 +1,50 @@
+/**
+ * An apex class that creates a portal user
+ */
+public with sharing class SiteRegisterController {
+ // PORTAL_ACCOUNT_ID is the account on which the contact will be created on and then enabled as a portal user.
+ // you need to add the account owner into the role hierarchy before this will work - please see Customer Portal Setup help for more information.
+ private static Id PORTAL_ACCOUNT_ID = '001x000xxx35tPN';
+
+ public SiteRegisterController () {
+ }
+
+ public String username {get; set;}
+ public String email {get; set;}
+ public String password {get; set {password = value == null ? value : value.trim(); } }
+ public String confirmPassword {get; set { confirmPassword = value == null ? value : value.trim(); } }
+ public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } }
+
+ private boolean isValidPassword() {
+ return password == confirmPassword;
+ }
+
+ public PageReference registerUser() {
+ // it's okay if password is null - we'll send the user a random password in that case
+ if (!isValidPassword()) {
+ ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match);
+ ApexPages.addMessage(msg);
+ return null;
+ }
+ User u = new User();
+ u.Username = username;
+ u.Email = email;
+ u.CommunityNickname = communityNickname;
+
+ String accountId = PORTAL_ACCOUNT_ID;
+
+ // lastName is a required field on user, but if it isn't specified, we'll default it to the username
+ String userId = Site.createPortalUser(u, accountId, password);
+ if (userId != null) {
+ if (password != null && password.length() > 1) {
+ return Site.login(username, password, null);
+ }
+ else {
+ PageReference page = System.Page.SiteRegisterConfirm;
+ page.setRedirect(true);
+ return page;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/SiteRegisterController.cls-meta.xml b/examples/main/default/classes/SiteRegisterController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/SiteRegisterController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/classes/SiteRegisterControllerTest.cls b/examples/main/default/classes/SiteRegisterControllerTest.cls
new file mode 100644
index 0000000..79f27fa
--- /dev/null
+++ b/examples/main/default/classes/SiteRegisterControllerTest.cls
@@ -0,0 +1,17 @@
+/**
+ * Class containing tests for SiteRegisterController
+ */
+@IsTest public with sharing class SiteRegisterControllerTest {
+ @IsTest(SeeAllData=true) static void testRegistration() {
+ SiteRegisterController controller = new SiteRegisterController();
+ controller.username = 'test@force.com';
+ controller.email = 'test@force.com';
+ controller.communityNickname = 'test';
+ // registerUser will always return null when the page isn't accessed as a guest user
+ System.assert(controller.registerUser() == null);
+
+ controller.password = 'abcd1234';
+ controller.confirmPassword = 'abcd123';
+ System.assert(controller.registerUser() == null);
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml b/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/main/default/classes/SiteRegisterControllerTest.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/main/default/components/SiteFooter.component b/examples/main/default/components/SiteFooter.component
new file mode 100644
index 0000000..2023a51
--- /dev/null
+++ b/examples/main/default/components/SiteFooter.component
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/examples/main/default/components/SiteFooter.component-meta.xml b/examples/main/default/components/SiteFooter.component-meta.xml
new file mode 100644
index 0000000..b4861b0
--- /dev/null
+++ b/examples/main/default/components/SiteFooter.component-meta.xml
@@ -0,0 +1,6 @@
+
+
+ 60.0
+ Default Lightning Platform site footer component
+ SiteFooter
+
diff --git a/examples/main/default/components/SiteHeader.component b/examples/main/default/components/SiteHeader.component
new file mode 100644
index 0000000..3ebfbe7
--- /dev/null
+++ b/examples/main/default/components/SiteHeader.component
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/examples/main/default/components/SiteHeader.component-meta.xml b/examples/main/default/components/SiteHeader.component-meta.xml
new file mode 100644
index 0000000..6a95c88
--- /dev/null
+++ b/examples/main/default/components/SiteHeader.component-meta.xml
@@ -0,0 +1,6 @@
+
+
+ 60.0
+ Default Lightning Platform site header component
+ SiteHeader
+
diff --git a/examples/main/default/components/SiteLogin.component b/examples/main/default/components/SiteLogin.component
new file mode 100644
index 0000000..ec891e0
--- /dev/null
+++ b/examples/main/default/components/SiteLogin.component
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!$Label.site.forgot_your_password_q}
+
+ {!$Label.site.new_user_q}
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/components/SiteLogin.component-meta.xml b/examples/main/default/components/SiteLogin.component-meta.xml
new file mode 100644
index 0000000..1ecdc38
--- /dev/null
+++ b/examples/main/default/components/SiteLogin.component-meta.xml
@@ -0,0 +1,6 @@
+
+
+ 60.0
+ Default Salesforce Sites Login component
+ SiteLogin
+
diff --git a/examples/main/default/components/SitePoweredBy.component b/examples/main/default/components/SitePoweredBy.component
new file mode 100644
index 0000000..52b3779
--- /dev/null
+++ b/examples/main/default/components/SitePoweredBy.component
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/components/SitePoweredBy.component-meta.xml b/examples/main/default/components/SitePoweredBy.component-meta.xml
new file mode 100644
index 0000000..0fcf410
--- /dev/null
+++ b/examples/main/default/components/SitePoweredBy.component-meta.xml
@@ -0,0 +1,6 @@
+
+
+ 60.0
+ Default Lightning Platform site powered by component
+ SitePoweredBy
+
diff --git a/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml b/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml
new file mode 100644
index 0000000..bb1f9f6
--- /dev/null
+++ b/examples/main/default/cspTrustedSites/Tailwind.cspTrustedSite-meta.xml
@@ -0,0 +1,14 @@
+
+
+ false
+ false
+ All
+ https://tailwindui.com
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+
diff --git a/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml b/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml
new file mode 100644
index 0000000..030c85e
--- /dev/null
+++ b/examples/main/default/digitalExperienceConfigs/examples1.digitalExperienceConfig-meta.xml
@@ -0,0 +1,8 @@
+
+
+ examples
+
+
+
+ site/examples1
+
diff --git a/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml b/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml
new file mode 100644
index 0000000..eed22ae
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/examples1.digitalExperience-meta.xml
@@ -0,0 +1,4 @@
+
+
+ examples1
+
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json
new file mode 100644
index 0000000..676a917
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "mainAppPage",
+ "type" : "sfdc_cms__appPage",
+ "path" : ""
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json
new file mode 100644
index 0000000..346fe17
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__appPage/mainAppPage/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__appPage",
+ "title" : "main",
+ "contentBody" : {
+ "currentThemeId" : "Build_Your_Own_LWR",
+ "headMarkup" : " \n \nWelcome to LWC Communities! \n\n\n\r\n \n\r\n \n \n \n\n\n\n\n\n \n \n",
+ "isLockerServiceEnabled" : true,
+ "isRelaxedCSPLevel" : false,
+ "templateName" : "talon-template-byo"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json
new file mode 100644
index 0000000..ab11b60
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Build_Your_Own_LWR",
+ "type" : "sfdc_cms__brandingSet",
+ "path" : "brandingSets"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json
new file mode 100644
index 0000000..e626fec
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__brandingSet/Build_Your_Own_LWR/content.json
@@ -0,0 +1,183 @@
+{
+ "type" : "sfdc_cms__brandingSet",
+ "title" : "Build Your Own (LWR)",
+ "contentBody" : {
+ "brandingSetType" : "APP",
+ "definitionName" : "talon-template-byo:branding",
+ "values" : {
+ "BackgroundColor" : "#ffffff",
+ "BaseFontSize" : "1rem",
+ "BodyFont" : "Salesforce Sans",
+ "BodyFontSize" : "1rem",
+ "BodyFontStyle" : "normal",
+ "BodyFontWeight" : "400",
+ "BodyLetterSpacing" : "0em",
+ "BodyLineHeight" : "1.5",
+ "BodySmallFont" : "Salesforce Sans",
+ "BodySmallFontSize" : "0.75rem",
+ "BodySmallFontStyle" : "normal",
+ "BodySmallFontWeight" : "400",
+ "BodySmallLetterSpacing" : "0em",
+ "BodySmallLineHeight" : "1.25",
+ "BodySmallTextColor" : "var(--dxp-g-root-contrast)",
+ "BodySmallTextDecoration" : "none",
+ "BodySmallTextTransform" : "none",
+ "BodyTextColor" : "var(--dxp-g-root-contrast)",
+ "BodyTextDecoration" : "none",
+ "BodyTextTransform" : "none",
+ "ButtonActiveColor" : "var(--dxp-s-button-color-1)",
+ "ButtonBorderRadius" : "4px",
+ "ButtonColor" : "var(--dxp-g-brand)",
+ "ButtonFocusColor" : "var(--dxp-s-button-color-1)",
+ "ButtonFont" : "Salesforce Sans",
+ "ButtonFontSize" : "1rem",
+ "ButtonFontStyle" : "normal",
+ "ButtonFontWeight" : "400",
+ "ButtonHoverColor" : "var(--dxp-s-button-color-1)",
+ "ButtonLargeBorderRadius" : "4px",
+ "ButtonLargeFontSize" : "1.25rem",
+ "ButtonLargePadding" : "1.25rem",
+ "ButtonLetterSpacing" : "0em",
+ "ButtonLineHeight" : "2",
+ "ButtonPadding" : "1rem",
+ "ButtonSmallBorderRadius" : "4px",
+ "ButtonSmallFontSize" : "0.75rem",
+ "ButtonSmallPadding" : "0.75rem",
+ "ButtonTextTransform" : "none",
+ "ColumnSpacerSizeDesktop" : "1rem",
+ "ColumnSpacerSizeMobile" : "0.75rem",
+ "ComponentSpacerSizeDesktop" : "1.5rem",
+ "ComponentSpacerSizeMobile" : "1.5rem",
+ "DropdownBackgroundColor" : "var(--dxp-g-root)",
+ "DropdownBackgroundHoverColor" : "var(--dxp-g-neutral)",
+ "DropdownBorderColor" : "var(--dxp-g-neutral)",
+ "DropdownTextColor" : "var(--dxp-g-root-contrast)",
+ "DropdownTextHoverColor" : "var(--dxp-g-neutral-contrast)",
+ "FormElementBackgroundColor" : "var(--dxp-g-root)",
+ "FormElementBorderColor" : "var(--dxp-g-neutral-3)",
+ "FormElementBorderRadius" : "4px",
+ "FormElementBorderWidth" : "1px",
+ "FormElementLabelColor" : "var(--dxp-g-root-contrast)",
+ "FormElementTextColor" : "var(--dxp-g-root-contrast)",
+ "HeadingExtraLargeColor" : "var(--dxp-g-root-contrast)",
+ "HeadingExtraLargeFont" : "Salesforce Sans",
+ "HeadingExtraLargeFontSize" : "2.5rem",
+ "HeadingExtraLargeFontStyle" : "normal",
+ "HeadingExtraLargeFontWeight" : "300",
+ "HeadingExtraLargeLetterSpacing" : "0em",
+ "HeadingExtraLargeLineHeight" : "1.25",
+ "HeadingExtraLargeTextDecoration" : "none",
+ "HeadingExtraLargeTextTransform" : "none",
+ "HeadingLargeColor" : "var(--dxp-g-root-contrast)",
+ "HeadingLargeFont" : "Salesforce Sans",
+ "HeadingLargeFontSize" : "1.75rem",
+ "HeadingLargeFontStyle" : "normal",
+ "HeadingLargeFontWeight" : "300",
+ "HeadingLargeLetterSpacing" : "0em",
+ "HeadingLargeLineHeight" : "1.25",
+ "HeadingLargeTextDecoration" : "none",
+ "HeadingLargeTextTransform" : "none",
+ "HeadingMediumColor" : "var(--dxp-g-root-contrast)",
+ "HeadingMediumFont" : "Salesforce Sans",
+ "HeadingMediumFontSize" : "1.25rem",
+ "HeadingMediumFontStyle" : "normal",
+ "HeadingMediumFontWeight" : "300",
+ "HeadingMediumLetterSpacing" : "0em",
+ "HeadingMediumLineHeight" : "1.25",
+ "HeadingMediumTextDecoration" : "none",
+ "HeadingMediumTextTransform" : "none",
+ "HeadingSmallColor" : "var(--dxp-g-root-contrast)",
+ "HeadingSmallFont" : "Salesforce Sans",
+ "HeadingSmallFontSize" : "1.125rem",
+ "HeadingSmallFontStyle" : "normal",
+ "HeadingSmallFontWeight" : "300",
+ "HeadingSmallLetterSpacing" : "0em",
+ "HeadingSmallLineHeight" : "1.25",
+ "HeadingSmallTextDecoration" : "none",
+ "HeadingSmallTextTransform" : "none",
+ "HorizontalRowPaddingDesktop" : "1rem",
+ "HorizontalRowPaddingMobile" : "0.75rem",
+ "LinkColor" : "var(--dxp-g-brand)",
+ "LinkHoverColor" : "var(--dxp-s-link-text-color-1)",
+ "LinkTextDecoration" : "none",
+ "LinkTextDecorationFocus" : "underline",
+ "LinkTextDecorationHover" : "underline",
+ "MaxContentWidthDesktop" : "1800px",
+ "MaxContentWidthMobile" : "none",
+ "MobileBaseFontSize" : "1rem",
+ "PrimaryAccentColor" : "#005fb2",
+ "PrimaryAccentForegroundColor" : "#ffffff",
+ "SiteLogo" : "",
+ "TextColor" : "#1a1b1e",
+ "VerticalRowPaddingDesktop" : "1rem",
+ "VerticalRowPaddingMobile" : "0.75rem",
+ "_BackgroundColor1" : "#ebebeb",
+ "_BackgroundColor2" : "#c2c2c2",
+ "_BackgroundColor3" : "#858585",
+ "_ButtonActiveColorContrast" : "var(--dxp-g-brand-contrast-1)",
+ "_ButtonColor1" : "var(--dxp-g-brand-1)",
+ "_ButtonColorContrast" : "var(--dxp-g-brand-contrast)",
+ "_ButtonFocusColorContrast" : "var(--dxp-g-brand-contrast-1)",
+ "_ButtonHoverColorContrast" : "var(--dxp-g-brand-contrast-1)",
+ "_DestructiveColor" : "#c23934",
+ "_DestructiveColor1" : "#a2302b",
+ "_DestructiveColor2" : "#611d1a",
+ "_DestructiveColor3" : "#010000",
+ "_DestructiveForegroundColor" : "#ffffff",
+ "_DestructiveForegroundColor1" : "#ffffff",
+ "_DestructiveForegroundColor2" : "#ffffff",
+ "_DestructiveForegroundColor3" : "#ffffff",
+ "_InfoColor" : "#16325c",
+ "_InfoColor1" : "#0e203b",
+ "_InfoColor2" : "#000000",
+ "_InfoColor3" : "#000000",
+ "_InfoForegroundColor" : "#ffffff",
+ "_InfoForegroundColor1" : "#ffffff",
+ "_InfoForegroundColor2" : "#ffffff",
+ "_InfoForegroundColor3" : "#ffffff",
+ "_LinkColor1" : "var(--dxp-g-brand-1)",
+ "_NeutralColor" : "#ecebea",
+ "_NeutralColor1" : "#d9d7d5",
+ "_NeutralColor2" : "#b2aeaa",
+ "_NeutralColor3" : "#76716b",
+ "_NeutralForegroundColor" : "#000000",
+ "_NeutralForegroundColor1" : "#000000",
+ "_NeutralForegroundColor2" : "#000000",
+ "_NeutralForegroundColor3" : "#ffffff",
+ "_OfflineColor" : "#444444",
+ "_OfflineColor1" : "#303030",
+ "_OfflineColor2" : "#070707",
+ "_OfflineColor3" : "#000000",
+ "_OfflineForegroundColor" : "#ffffff",
+ "_OfflineForegroundColor1" : "#ffffff",
+ "_OfflineForegroundColor2" : "#ffffff",
+ "_OfflineForegroundColor3" : "#ffffff",
+ "_PrimaryAccentColor1" : "#004989",
+ "_PrimaryAccentColor2" : "#001e38",
+ "_PrimaryAccentColor3" : "#000000",
+ "_PrimaryAccentForegroundColor1" : "#ffffff",
+ "_PrimaryAccentForegroundColor2" : "#ffffff",
+ "_PrimaryAccentForegroundColor3" : "#ffffff",
+ "_SiteLogoUrl" : "",
+ "_SuccessColor" : "#4bca81",
+ "_SuccessColor1" : "#36b66c",
+ "_SuccessColor2" : "#237747",
+ "_SuccessColor3" : "#07190f",
+ "_SuccessForegroundColor" : "#000000",
+ "_SuccessForegroundColor1" : "#000000",
+ "_SuccessForegroundColor2" : "#ffffff",
+ "_SuccessForegroundColor3" : "#ffffff",
+ "_TextColor1" : "#000000",
+ "_TextColor2" : "#000000",
+ "_TextColor3" : "#000000",
+ "_WarningColor" : "#ffb75d",
+ "_WarningColor1" : "#ffa534",
+ "_WarningColor2" : "#e27d00",
+ "_WarningColor3" : "#673900",
+ "_WarningForegroundColor" : "#000000",
+ "_WarningForegroundColor1" : "#000000",
+ "_WarningForegroundColor2" : "#000000",
+ "_WarningForegroundColor3" : "#ffffff"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json
new file mode 100644
index 0000000..d0381dc
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "languages",
+ "type" : "sfdc_cms__languageSettings",
+ "path" : "_settings"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json
new file mode 100644
index 0000000..97abb57
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__languageSettings/languages/content.json
@@ -0,0 +1,13 @@
+{
+ "type" : "sfdc_cms__languageSettings",
+ "title" : "LanguageContent",
+ "contentBody" : {
+ "languages" : [ {
+ "locale" : "en_US",
+ "label" : "English (US)",
+ "isActive" : true,
+ "isAuthoringOnly" : false
+ } ],
+ "defaultLocale" : "en_US"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json
new file mode 100644
index 0000000..0fa36bf
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "mobilePublisherConfig",
+ "type" : "sfdc_cms__mobilePublisherConfig",
+ "path" : "_settings"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json
new file mode 100644
index 0000000..a8b205d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__mobilePublisherConfig/mobilePublisherConfig/content.json
@@ -0,0 +1,46 @@
+{
+ "type" : "sfdc_cms__mobilePublisherConfig",
+ "title" : "Mobile Publisher Content",
+ "contentBody" : {
+ "nativeTabMenu" : {
+ "branding" : {
+ "iconTintColor" : "#0B5CAB",
+ "iconTintColorUnselected" : "#C9C9C9",
+ "barTintColor" : "#FFFFFF"
+ },
+ "menuItems" : [ {
+ "name" : "Home",
+ "targetUrl" : "/",
+ "iconDetails" : {
+ "source" : {
+ "type" : "salesforceAsset",
+ "salesforceAssetName" : "slds-icon:home"
+ }
+ }
+ } ]
+ },
+ "nativeMobileNavConfig" : {
+ "global" : {
+ "showHamburgerMenu" : true
+ },
+ "ios" : {
+ "showBackButton" : true,
+ "showHamburgerMenuWithBackButton" : false
+ }
+ },
+ "mobilePublisherAppUpdateConfig" : {
+ "enableAppUpdate" : false,
+ "forceAppUpdate" : false,
+ "minVersion" : {
+ "ios" : {
+ "url" : "https://apps.apple.com/us",
+ "version" : "10.0"
+ },
+ "android" : {
+ "url" : "https://play.google.com/store",
+ "version" : "10.1"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json
new file mode 100644
index 0000000..f3856da
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Check_Password",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json
new file mode 100644
index 0000000..7bac5cb
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Check_Password/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Check Password",
+ "contentBody" : {
+ "activeViewId" : "checkPasswordResetEmail",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "check-password",
+ "urlPrefix" : "CheckPasswordResetEmail"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json
new file mode 100644
index 0000000..0df70e2
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Checkout__c",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json
new file mode 100644
index 0000000..38bb450
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Checkout__c/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Checkout",
+ "contentBody" : {
+ "activeViewId" : "Checkout",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "custom-checkout",
+ "urlPrefix" : "checkout"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json
new file mode 100644
index 0000000..5ce69b0
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Error",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json
new file mode 100644
index 0000000..879079a
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Error/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Error",
+ "contentBody" : {
+ "activeViewId" : "error",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "error",
+ "urlPrefix" : "error"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json
new file mode 100644
index 0000000..e421ecb
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Forgot_Password",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json
new file mode 100644
index 0000000..66d9662
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Forgot_Password/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Forgot Password",
+ "contentBody" : {
+ "activeViewId" : "forgotPassword",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "forgot-password",
+ "urlPrefix" : "ForgotPassword"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json
new file mode 100644
index 0000000..cd9f8a4
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Home",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json
new file mode 100644
index 0000000..bc24463
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Home/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Home",
+ "contentBody" : {
+ "activeViewId" : "home",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "home",
+ "urlPrefix" : ""
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json
new file mode 100644
index 0000000..ad05a8c
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Login",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json
new file mode 100644
index 0000000..a019936
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Login/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Login",
+ "contentBody" : {
+ "activeViewId" : "login",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "login-main",
+ "urlPrefix" : "login"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json
new file mode 100644
index 0000000..6101581
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "News_Detail__c",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json
new file mode 100644
index 0000000..97d5d6d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/News_Detail__c/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "News Detail",
+ "contentBody" : {
+ "activeViewId" : "newsDetail",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "managed-content-sfdc_cms__news",
+ "urlPrefix" : "news"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json
new file mode 100644
index 0000000..bce40ba
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Register",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json
new file mode 100644
index 0000000..81cd435
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Register/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Register",
+ "contentBody" : {
+ "activeViewId" : "register",
+ "configurationTags" : [ ],
+ "pageAccess" : "UseParent",
+ "routeType" : "self-register",
+ "urlPrefix" : "SelfRegister"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json
new file mode 100644
index 0000000..6ec0efb
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Service_Not_Available",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json
new file mode 100644
index 0000000..ddedb11
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Service_Not_Available/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Service Not Available",
+ "contentBody" : {
+ "activeViewId" : "serviceNotAvailable",
+ "configurationTags" : [ "allow-in-static-site" ],
+ "pageAccess" : "UseParent",
+ "routeType" : "service-not-available",
+ "urlPrefix" : "service-not-available"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json
new file mode 100644
index 0000000..2d88e32
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Too_Many_Requests",
+ "type" : "sfdc_cms__route",
+ "path" : "routes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json
new file mode 100644
index 0000000..42413d9
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__route/Too_Many_Requests/content.json
@@ -0,0 +1,11 @@
+{
+ "type" : "sfdc_cms__route",
+ "title" : "Too Many Requests",
+ "contentBody" : {
+ "activeViewId" : "tooManyRequests",
+ "configurationTags" : [ "too-many-requests", "allow-in-static-site" ],
+ "pageAccess" : "UseParent",
+ "routeType" : "too-many-requests",
+ "urlPrefix" : "too-many-requests"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json
new file mode 100644
index 0000000..cb37c96
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "examples1",
+ "type" : "sfdc_cms__site",
+ "path" : ""
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json
new file mode 100644
index 0000000..b286a50
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__site/examples1/content.json
@@ -0,0 +1,7 @@
+{
+ "type" : "sfdc_cms__site",
+ "title" : "examples",
+ "contentBody" : {
+ "authenticationType" : "AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json
new file mode 100644
index 0000000..9fa3d0d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "print_css",
+ "type" : "sfdc_cms__styles",
+ "path" : ""
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json
new file mode 100644
index 0000000..3751772
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/content.json
@@ -0,0 +1,15 @@
+{
+ "type" : "sfdc_cms__styles",
+ "title" : "print.css",
+ "contentBody" : {
+ "sfdc_cms:media" : {
+ "source" : {
+ "type" : "file",
+ "ref" : "0sN7z0000004OF7",
+ "size" : 1013,
+ "mimeType" : "text/css"
+ },
+ "url" : "/cms/media/MCNHKZKVNYSRGLDJDYQCX7PXJEHA?version=1.1"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css
new file mode 100644
index 0000000..37937fe
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/print_css/print.css
@@ -0,0 +1,36 @@
+@media print {
+ /* Hide href for all links */
+ /* a[href]:after {
+ content: none !important;
+ } */
+
+ /* Example: hides header and footer for Customer Service template */
+ /* #header, #footer {
+ display: none !important
+ } */
+
+ /* Example: hides header for Customer Account and Partner Central template */
+ /* .cHeaderWrapper, .cFooterPanel {
+ display: none !important;
+ } */
+
+ /* Example: hides header and footer for Help Center template */
+ /* .header, .footer {
+ display: none !important;
+ } */
+
+ /* Example: hides header and footer for Build Your Own (Aura) template */
+ /* .comm-content-header, .comm-content-footer {
+ display: none !important;
+ } */
+
+ /* Example: hides header and footer for LWR templates */
+ /* header, footer {
+ display: none !important;
+ } */
+
+ /* Example: hides the aura Navigation Menu component */
+ /* .forceCommunityGlobalNavigation {
+ display: none !important;
+ } */
+}
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json
new file mode 100644
index 0000000..95f382d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "styles_css",
+ "type" : "sfdc_cms__styles",
+ "path" : ""
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json
new file mode 100644
index 0000000..115b776
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/content.json
@@ -0,0 +1,15 @@
+{
+ "type" : "sfdc_cms__styles",
+ "title" : "styles.css",
+ "contentBody" : {
+ "sfdc_cms:media" : {
+ "source" : {
+ "type" : "file",
+ "ref" : "0sN7z0000004OF6",
+ "size" : 125,
+ "mimeType" : "text/css"
+ },
+ "url" : "/cms/media/MC6V5FDF6HDRG25KCS5L4Y7EOAKU?version=1.1"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css
new file mode 100644
index 0000000..519a43f
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__styles/styles_css/styles.css
@@ -0,0 +1,15 @@
+/*
+ * Your global styles here
+ */
+html,
+body {
+ height: 100%;
+}
+
+html {
+ background: white;
+}
+
+body {
+ margin: 0;
+}
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json
new file mode 100644
index 0000000..200d629
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Build_Your_Own_LWR",
+ "type" : "sfdc_cms__theme",
+ "path" : "themes"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json
new file mode 100644
index 0000000..5761516
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__theme/Build_Your_Own_LWR/content.json
@@ -0,0 +1,15 @@
+{
+ "type" : "sfdc_cms__theme",
+ "title" : "Build Your Own (LWR)",
+ "contentBody" : {
+ "activeBrandingSetId" : "Build_Your_Own_LWR",
+ "definitionName" : "byo",
+ "layouts" : [ {
+ "layoutId" : "snaThemeLayout",
+ "layoutType" : "ServiceNotAvailable"
+ }, {
+ "layoutId" : "scopedHeaderAndFooter",
+ "layoutType" : "Inner"
+ } ]
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json
new file mode 100644
index 0000000..90769ae
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "scopedHeaderAndFooter",
+ "type" : "sfdc_cms__themeLayout",
+ "path" : "themeLayouts"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json
new file mode 100644
index 0000000..ad947ef
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json
@@ -0,0 +1,61 @@
+{
+ "type" : "sfdc_cms__themeLayout",
+ "title" : "Scoped Header and Footer",
+ "contentBody" : {
+ "component" : {
+ "attributes" : { },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"65c6e3b6-533c-4eb2-b535-24dfdcd989f5\",\"columns\":[{\"UUID\":\"d2eddd69-b9a2-4fd0-8877-2075ab61251c\",\"columnName\":\"Column 1\",\"columnKey\":\"headerSection\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "id" : "d2eddd69-b9a2-4fd0-8877-2075ab61251c",
+ "name" : "headerSection",
+ "title" : "Theme Header",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "65c6e3b6-533c-4eb2-b535-24dfdcd989f5",
+ "type" : "component"
+ } ],
+ "id" : "148290c6-fa59-4c00-9db5-e1243d78c25a",
+ "name" : "header",
+ "title" : "Theme Header",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"fa19eed3-c2f9-479e-8e5c-dfe81ac8b8f6\",\"columns\":[{\"UUID\":\"0a06af77-6814-47ec-86ed-7772ca96098b\",\"columnName\":\"Column 1\",\"columnKey\":\"footerSection\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "id" : "0a06af77-6814-47ec-86ed-7772ca96098b",
+ "name" : "footerSection",
+ "title" : "Theme Footer",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "fa19eed3-c2f9-479e-8e5c-dfe81ac8b8f6",
+ "type" : "component"
+ } ],
+ "id" : "a7a4d1a4-7ea6-4776-b1f4-123a66c838c5",
+ "name" : "footer",
+ "title" : "Theme Footer",
+ "type" : "region"
+ } ],
+ "definition" : "community_byo:scopedHeaderAndFooter",
+ "id" : "84e48005-22ec-4b53-b058-4d62e0419d40",
+ "type" : "component"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json
new file mode 100644
index 0000000..ac7ee60
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "snaThemeLayout",
+ "type" : "sfdc_cms__themeLayout",
+ "path" : "themeLayouts"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json
new file mode 100644
index 0000000..b6bc9ba
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__themeLayout/snaThemeLayout/content.json
@@ -0,0 +1,23 @@
+{
+ "type" : "sfdc_cms__themeLayout",
+ "title" : "Service Not Available Theme Layout",
+ "contentBody" : {
+ "component" : {
+ "attributes" : { },
+ "children" : [ {
+ "id" : "95b3aae8-a63f-4f2a-8366-45d43b492d80",
+ "name" : "header",
+ "title" : "Theme Header",
+ "type" : "region"
+ }, {
+ "id" : "d6cbb1e9-b32e-4de4-8b36-a5cff8c32809",
+ "name" : "footer",
+ "title" : "Theme Footer",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:simpleThemeLayout",
+ "id" : "48bc63c6-e678-4938-9cea-c3edd83b3112",
+ "type" : "component"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json
new file mode 100644
index 0000000..aadb55b
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "Checkout",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json
new file mode 100644
index 0000000..6a955ac
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/Checkout/content.json
@@ -0,0 +1,71 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Checkout",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"0130a194-0b9d-4194-8e2c-47f627cafca8\",\"columns\":[{\"UUID\":\"2d6af6c8-95f7-4297-9ee9-6d33f0258647\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : { },
+ "definition" : "c:shoppingCartDetails",
+ "id" : "bf9c3261-d87f-47e6-b000-8b2597a51496",
+ "type" : "component"
+ }, {
+ "attributes" : { },
+ "definition" : "c:orderSummary",
+ "id" : "4c59e794-6e57-4802-81ef-d11f0c503241",
+ "type" : "component"
+ }, {
+ "attributes" : { },
+ "definition" : "c:checkoutButton",
+ "id" : "3ddd65b5-d783-4ecb-9921-91709428a735",
+ "type" : "component"
+ } ],
+ "id" : "2d6af6c8-95f7-4297-9ee9-6d33f0258647",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "0130a194-0b9d-4194-8e2c-47f627cafca8",
+ "type" : "component"
+ } ],
+ "id" : "20d6af3d-694c-465d-8498-c68b961a80d7",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Checkout",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "1f185f02-ddd3-4137-8f99-4b3508ad79c5",
+ "type" : "component"
+ } ],
+ "id" : "87d27a2a-dc32-4142-b244-e5cd51ec16b8",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "c791f994-38f9-41dc-a433-2149ea0969aa",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "custom-checkout"
+ }
+}
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json
new file mode 100644
index 0000000..9135294
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "checkPasswordResetEmail",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json
new file mode 100644
index 0000000..06a36d4
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/checkPasswordResetEmail/content.json
@@ -0,0 +1,74 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Check Password",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"86788f8c-779f-42c3-ad27-8124aa3b2a71\",\"columns\":[{\"UUID\":\"645bff85-5f13-4a97-b1d7-ffc10e5e10fb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "alignment" : "left",
+ "imageInfo" : "",
+ "logoWidth" : 250
+ },
+ "definition" : "dxp_content_layout:siteLogo",
+ "id" : "05c35e7f-e530-41e5-8b38-554c95323454",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "checkEmailMessage" : "Check the email account associated with your username for the link to reset your password. If you didn't get an email, check your Spam folder. Or contact your administrator.",
+ "returnButtonLabel" : "Back to login",
+ "titleLabel" : "Now check your email"
+ },
+ "definition" : "community_login:checkEmail",
+ "id" : "9f2d6f92-8e2e-4929-bc5d-67ef7b125cb0",
+ "type" : "component"
+ } ],
+ "id" : "645bff85-5f13-4a97-b1d7-ffc10e5e10fb",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "86788f8c-779f-42c3-ad27-8124aa3b2a71",
+ "type" : "component"
+ } ],
+ "id" : "df649a2c-6f89-45a0-93d0-e6129a444004",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Check Password",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "39c2dc11-b5cd-4c25-855f-0739c06436fa",
+ "type" : "component"
+ } ],
+ "id" : "140f175d-8005-47ce-aacd-38f00b6cfec1",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "1f0fa9e1-6e98-4c32-abca-6116a231ecca",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "check-password"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json
new file mode 100644
index 0000000..f9287cb
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "error",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json
new file mode 100644
index 0000000..8c20c8f
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/error/content.json
@@ -0,0 +1,63 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Error",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"7c1d48be-3535-416c-b05a-b1a3e4c4facd\",\"columns\":[{\"UUID\":\"d860137b-2783-420d-88ea-8b7144e752b2\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "richTextValue" : "Invalid Page "
+ },
+ "definition" : "community_builder:richTextEditor",
+ "id" : "348080be-692f-4ea7-b2dc-92edb50248c3",
+ "type" : "component"
+ } ],
+ "id" : "d860137b-2783-420d-88ea-8b7144e752b2",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "7c1d48be-3535-416c-b05a-b1a3e4c4facd",
+ "type" : "component"
+ } ],
+ "id" : "515ec8a6-2dfe-461f-a2e3-1c615bc3ab6a",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Error",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "54e69473-45ee-4b4d-89e0-b0ccc0ebd6ad",
+ "type" : "component"
+ } ],
+ "id" : "2ad828de-2c3e-4092-af2d-c2fc7a450654",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "24b4958a-34f0-4b92-beb2-0256971dfef2",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "error"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json
new file mode 100644
index 0000000..dd6ab2c
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "forgotPassword",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json
new file mode 100644
index 0000000..414b5ab
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/forgotPassword/content.json
@@ -0,0 +1,77 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Forgot Password",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"126ea3c4-03c1-4520-8c52-941678989028\",\"columns\":[{\"UUID\":\"54219043-cc18-46c4-91a7-25d6d5bc4ec7\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "alignment" : "left",
+ "imageInfo" : "",
+ "logoWidth" : 250
+ },
+ "definition" : "dxp_content_layout:siteLogo",
+ "id" : "f7a4992d-2bfa-453f-9d35-b9148cee814b",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "cancelButtonLabel" : "Cancel",
+ "checkEmailUrl" : "./CheckPasswordResetEmail",
+ "instructionsLabel" : "To reset your password, enter your username. We'll send a reset-password link to the email address associated with your account.",
+ "submitButtonLabel" : "Reset",
+ "titleLabel" : "Forgot your password?",
+ "usernameLabel" : "Username"
+ },
+ "definition" : "community_login:forgotPassword",
+ "id" : "871a0b8a-9b04-4bb1-b12b-edf66f2fe4c4",
+ "type" : "component"
+ } ],
+ "id" : "54219043-cc18-46c4-91a7-25d6d5bc4ec7",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "126ea3c4-03c1-4520-8c52-941678989028",
+ "type" : "component"
+ } ],
+ "id" : "36b80763-fb3b-42e6-8a5a-bd76273f133e",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Forgot Password",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "418e023a-7db1-4606-9b60-8282d3444e7f",
+ "type" : "component"
+ } ],
+ "id" : "df768e12-6c2e-4602-8953-eb0fbaeee214",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "c9cd673c-2d7d-414a-987e-2b3f2435397d",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "forgot-password"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json
new file mode 100644
index 0000000..0ebd40d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "home",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json
new file mode 100644
index 0000000..bde8046
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/home/content.json
@@ -0,0 +1,83 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Home",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"45e97f2a-56ff-4067-a47d-20024a315ba1\",\"columns\":[{\"UUID\":\"142284b4-88b0-4d00-ad59-6d177998602a\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "alignment" : "center",
+ "size" : "standard",
+ "stretch" : "standard",
+ "text" : "Checkout Demo",
+ "type" : "tertiary",
+ "url" : "{\"linkInfo\":{\"pageReference\":{\"type\":\"comm__namedPage\",\"attributes\":{\"name\":\"Checkout__c\"}}}}"
+ },
+ "definition" : "dxp_base:button",
+ "id" : "c4f0a4d8-6c9a-43dc-83fd-1ea9be17ef6a",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "backgroundColor" : "",
+ "isLinkable" : false,
+ "linkAssistiveText" : "",
+ "paddingHorizontal" : "none",
+ "paddingVertical" : "none",
+ "text" : "Demonstrates how to create a custom storage solution that implements Undo functionality.",
+ "textAlign" : "center",
+ "textDecoration" : "{}",
+ "textDisplayInfo" : "{}"
+ },
+ "definition" : "dxp_base:textBlock",
+ "id" : "5073049b-5c6f-42a8-a358-3232df2d813a",
+ "type" : "component"
+ } ],
+ "id" : "142284b4-88b0-4d00-ad59-6d177998602a",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "45e97f2a-56ff-4067-a47d-20024a315ba1",
+ "type" : "component"
+ } ],
+ "id" : "2800c82b-3f6c-4862-a597-01804f32f2ee",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Home",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "9c309199-8027-456d-989e-488a15ea84dc",
+ "type" : "component"
+ } ],
+ "id" : "03aa3d9c-2baf-4bf4-b0fc-cff3c60e8da7",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "bfc761b0-179e-4a18-98df-38cacf6d459a",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "home"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json
new file mode 100644
index 0000000..f956f58
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "login",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json
new file mode 100644
index 0000000..9124a1a
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/login/content.json
@@ -0,0 +1,86 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Login",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"e6716faf-b263-4b5b-89d6-01993c62379e\",\"columns\":[{\"UUID\":\"1054f36e-a1f0-47db-a2d3-54270bdc43fb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "alignment" : "left",
+ "imageInfo" : "",
+ "logoWidth" : 250
+ },
+ "definition" : "dxp_content_layout:siteLogo",
+ "id" : "470ded86-d520-4d9e-8ce7-e7e24de27e36",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "forgotPasswordLabel" : "Forgot your password?",
+ "forgotPasswordUrl" : "/ForgotPassword",
+ "loginButtonLabel" : "Log In",
+ "passwordLabel" : "Password",
+ "selfRegisterLabel" : "Not a member?",
+ "selfRegisterUrl" : "/SelfRegister",
+ "startUrl" : "",
+ "usernameLabel" : "Username"
+ },
+ "definition" : "community_login:loginForm",
+ "id" : "60b8495c-a33d-4249-9e72-a8bf650fb3df",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "employeeLoginLinkLabel" : "Are you an employee? Log in"
+ },
+ "definition" : "community_login:employeeLoginLink",
+ "id" : "dca57b66-3f61-47bc-a4bd-f1690c74b3d7",
+ "type" : "component"
+ } ],
+ "id" : "1054f36e-a1f0-47db-a2d3-54270bdc43fb",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "e6716faf-b263-4b5b-89d6-01993c62379e",
+ "type" : "component"
+ } ],
+ "id" : "ecfcea9d-1eb2-479b-a269-6a4c24b60f12",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Login",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "5e943f0b-2ecd-460f-9f9a-8fc7acb07ae0",
+ "type" : "component"
+ } ],
+ "id" : "ef1272ef-2d53-4a4f-8f3b-4e59151dc087",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "a9320ee5-26d9-462e-a8b8-eec050afa249",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "login-main"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json
new file mode 100644
index 0000000..464e1cc
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "newsDetail",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json
new file mode 100644
index 0000000..245b4fc
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/newsDetail/content.json
@@ -0,0 +1,55 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "News Detail",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"963ee36d-a489-41c5-b6ab-bc229e452aea\",\"columns\":[{\"UUID\":\"9b8d5e6b-df0c-49eb-afa7-6ce8578de929\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}"
+ },
+ "children" : [ {
+ "id" : "9b8d5e6b-df0c-49eb-afa7-6ce8578de929",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "963ee36d-a489-41c5-b6ab-bc229e452aea",
+ "type" : "component"
+ } ],
+ "id" : "f109d914-e97f-4885-905f-984004665a5a",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "{!Content.contentTypeLabel}.{!Content.title}",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "da7f94ed-554c-4a9f-8f59-b11e63ed7092",
+ "type" : "component"
+ } ],
+ "id" : "c941a4e4-e6ed-4794-9061-1085b735cdba",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "0b0a3364-5403-4e78-8722-97792b5a9a50",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "managed-content-sfdc_cms__news"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json
new file mode 100644
index 0000000..c6e02c0
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "register",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json
new file mode 100644
index 0000000..96255e2
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/register/content.json
@@ -0,0 +1,80 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Register",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"9e5e4548-6ec4-45d7-bc0d-530400408df0\",\"columns\":[{\"UUID\":\"45862d24-7b1d-4c3e-a1f8-411e0efccccb\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "alignment" : "left",
+ "imageInfo" : "",
+ "logoWidth" : 250
+ },
+ "definition" : "dxp_content_layout:siteLogo",
+ "id" : "ea67ff87-33d6-489f-8e77-4890e075aba4",
+ "type" : "component"
+ }, {
+ "attributes" : {
+ "cancelLinkLabel" : "Already have an account?",
+ "confirmPasswordLabel" : "Confirm Password",
+ "emailLabel" : "Email",
+ "firstnameLabel" : "First Name",
+ "includePasswordField" : true,
+ "lastnameLabel" : "Last Name",
+ "passwordLabel" : "Create Password",
+ "regConfirmUrl" : "./CheckPasswordResetEmail",
+ "submitButtonLabel" : "Sign Up"
+ },
+ "definition" : "community_login:selfRegister",
+ "id" : "ab32d174-5fbf-4634-9af5-ae9e3747465b",
+ "type" : "component"
+ } ],
+ "id" : "45862d24-7b1d-4c3e-a1f8-411e0efccccb",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "9e5e4548-6ec4-45d7-bc0d-530400408df0",
+ "type" : "component"
+ } ],
+ "id" : "7a803148-0b06-48de-bcc8-d7b227e18bff",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ }, {
+ "children" : [ {
+ "attributes" : {
+ "customHeadTags" : "",
+ "description" : "",
+ "pageTitle" : "Register",
+ "recordId" : "{!recordId}"
+ },
+ "definition" : "community_builder:seoAssistant",
+ "id" : "6668dff5-3720-433b-abc2-7afc5ce80e45",
+ "type" : "component"
+ } ],
+ "id" : "f9220ce3-4f4c-416b-acb7-30630a584abd",
+ "name" : "sfdcHiddenRegion",
+ "title" : "sfdcHiddenRegion",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "884d8cae-04a5-435f-ba3b-d022b7ed511a",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "Inner",
+ "viewType" : "self-register"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json
new file mode 100644
index 0000000..c1e4111
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "serviceNotAvailable",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json
new file mode 100644
index 0000000..4199b3c
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/serviceNotAvailable/content.json
@@ -0,0 +1,47 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Service Not Available",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"35e27b49-6f9a-47df-a744-2633f3ae1270\",\"columns\":[{\"UUID\":\"23674f4c-9c38-45cd-9080-f349d4a7f050\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "richTextValue" : "\n\n\t
Looks like the site is temporarily unavailable \n\t
\n\t
Please try again in a bit.
\n
"
+ },
+ "definition" : "community_builder:htmlEditor",
+ "id" : "af94931c-e447-4120-b4f5-d652ca2b602b",
+ "type" : "component"
+ } ],
+ "id" : "23674f4c-9c38-45cd-9080-f349d4a7f050",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "35e27b49-6f9a-47df-a744-2633f3ae1270",
+ "type" : "component"
+ } ],
+ "id" : "07cb18c7-1eba-42b9-b274-e46a3a1413e7",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "c3609148-f3c2-4158-bc1a-6c8381778e9a",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "ServiceNotAvailable",
+ "viewType" : "service-not-available"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json
new file mode 100644
index 0000000..f23899d
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/_meta.json
@@ -0,0 +1,5 @@
+{
+ "apiName" : "tooManyRequests",
+ "type" : "sfdc_cms__view",
+ "path" : "views"
+}
\ No newline at end of file
diff --git a/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json
new file mode 100644
index 0000000..c9a1329
--- /dev/null
+++ b/examples/main/default/digitalExperiences/site/examples1/sfdc_cms__view/tooManyRequests/content.json
@@ -0,0 +1,52 @@
+{
+ "type" : "sfdc_cms__view",
+ "title" : "Too Many Requests",
+ "contentBody" : {
+ "component" : {
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "backgroundImageConfig" : "",
+ "backgroundImageOverlay" : "rgba(0,0,0,0)",
+ "componentSpacerSize" : "",
+ "maxContentWidth" : "",
+ "sectionColumnGutterWidth" : "",
+ "sectionConfig" : "{\"UUID\":\"a1fc7166-0b48-4928-9b6d-f6ad1f106725\",\"columns\":[{\"UUID\":\"dced948c-cf2b-449f-9384-f233da91d928\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
+ },
+ "children" : [ {
+ "children" : [ {
+ "attributes" : {
+ "richTextValue" : "\n\n\t
Looks like the site is experiencing higher than usual demand… \n\t
Don't go anywhere. We'll redirect you in a moment.
\n
"
+ },
+ "definition" : "community_builder:htmlEditor",
+ "id" : "f54bb58b-1070-48fe-921e-21f46304d250",
+ "type" : "component"
+ }, {
+ "attributes" : { },
+ "definition" : "experience_availability:autoRefresh",
+ "id" : "7e33bf4e-0a84-4cb9-8bf3-a03957a12774",
+ "type" : "component"
+ } ],
+ "id" : "dced948c-cf2b-449f-9384-f233da91d928",
+ "name" : "col1",
+ "title" : "Column 1",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:section",
+ "id" : "a1fc7166-0b48-4928-9b6d-f6ad1f106725",
+ "type" : "component"
+ } ],
+ "id" : "85d2dc00-5280-40a6-9ae7-b1014224ceda",
+ "name" : "content",
+ "title" : "Content",
+ "type" : "region"
+ } ],
+ "definition" : "community_layout:sldsFlexibleLayout",
+ "id" : "ef1fee5b-f825-43c0-81ea-8a71dcc99801",
+ "type" : "component"
+ },
+ "dataProviders" : [ ],
+ "themeLayoutType" : "ServiceNotAvailable",
+ "viewType" : "too-many-requests"
+ }
+}
\ No newline at end of file
diff --git a/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email
new file mode 100644
index 0000000..4fe71e1
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email
@@ -0,0 +1,8 @@
+Hi {!Receiving_User.FirstName},
+
+ Your password has been reset for {!Community_Name}. Go to:
+
+ {!Community_Url}
+
+ Thanks,
+ {!Organization.Name}
\ No newline at end of file
diff --git a/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml
new file mode 100644
index 0000000..c822d36
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityChangePasswordEmailTemplate.email-meta.xml
@@ -0,0 +1,11 @@
+
+
+ true
+ Notification of new password
+ UTF-8
+ Communities: Changed Password Email
+
+ Your new {!Community_Name} password
+ text
+ Aloha
+
diff --git a/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email
new file mode 100644
index 0000000..b7fb22b
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email
@@ -0,0 +1,8 @@
+Hi {!Receiving_User.FirstName},
+
+ Your password has been reset for {!Community_Name}. Go to:
+
+ {!Community_Url}
+
+ Thanks,
+ {!Organization.Name}
\ No newline at end of file
diff --git a/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml
new file mode 100644
index 0000000..16bd65b
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityForgotPasswordEmailTemplate.email-meta.xml
@@ -0,0 +1,11 @@
+
+
+ true
+ Notification of new password when a user’s password is reset (because they forgot it)
+ UTF-8
+ Communities: Forgot Password Email
+
+ Your new {!Community_Name} password
+ text
+ Aloha
+
diff --git a/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email
new file mode 100644
index 0000000..9f3df9a
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email
@@ -0,0 +1,8 @@
+Hi {!Receiving_User.FirstName},
+
+ Welcome to {!Community_Name}! To get started, go to {!Community_Url}
+
+ Username: {!Receiving_User.Username}
+
+ Thanks,
+ {!Organization.Name}
\ No newline at end of file
diff --git a/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml
new file mode 100644
index 0000000..cf0e582
--- /dev/null
+++ b/examples/main/default/email/unfiled$public/CommunityWelcomeEmailTemplate.email-meta.xml
@@ -0,0 +1,11 @@
+
+
+ true
+ Notification that user has been added to a community.
+ UTF-8
+ Communities: New Member Welcome Email
+
+ Welcome to {!Community_Name}
+ text
+ Aloha
+
diff --git a/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml b/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml
new file mode 100644
index 0000000..086758a
--- /dev/null
+++ b/examples/main/default/navigationMenus/SFDC_Default_Navigation_examples.navigationMenu-meta.xml
@@ -0,0 +1,13 @@
+
+
+ examples
+ Network
+ Default Navigation
+
+ Topics
+ 1
+ true
+ ShowMoreTopics
+ NavigationalTopic
+
+
diff --git a/examples/main/default/networkBranding/cbexamples.networkBranding b/examples/main/default/networkBranding/cbexamples.networkBranding
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/examples/main/default/networkBranding/cbexamples.networkBranding
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml b/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml
new file mode 100644
index 0000000..057c2bd
--- /dev/null
+++ b/examples/main/default/networkBranding/cbexamples.networkBranding-meta.xml
@@ -0,0 +1,13 @@
+
+
+ examples
+ #1797C0
+ #FFFFFF
+ #B1BAC1
+ #222222
+ #51606E
+ #DDE4E9
+ #222222
+ #51606E
+ #FFFFFF
+
diff --git a/examples/main/default/networks/examples.network-meta.xml b/examples/main/default/networks/examples.network-meta.xml
new file mode 100644
index 0000000..f567f34
--- /dev/null
+++ b/examples/main/default/networks/examples.network-meta.xml
@@ -0,0 +1,57 @@
+
+
+ false
+ false
+ unfiled$public/CommunityChangePasswordEmailTemplate
+
+ true
+ cesar.parra@gmail.com
+ examples
+ true
+ false
+ true
+ false
+ true
+ false
+ true
+ false
+ false
+ false
+ false
+ true
+ false
+ true
+ false
+ false
+ false
+ true
+ true
+ true
+ false
+ false
+ unfiled$public/CommunityForgotPasswordEmailTemplate
+ false
+ unfiled$public/CommunityForgotPasswordEmailTemplate
+
+ Admin
+
+
+ Standard
+ Designer
+ Designer
+ Designer
+ Designer
+
+ examples1
+ false
+ true
+ examples
+ NotArchived
+ UnderConstruction
+
+ home
+ Chatter
+
+ vforcesite
+ unfiled$public/CommunityWelcomeEmailTemplate
+
diff --git a/examples/main/default/pages/AnswersHome.page b/examples/main/default/pages/AnswersHome.page
new file mode 100644
index 0000000..a305b75
--- /dev/null
+++ b/examples/main/default/pages/AnswersHome.page
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/main/default/pages/AnswersHome.page-meta.xml b/examples/main/default/pages/AnswersHome.page-meta.xml
new file mode 100644
index 0000000..e07f895
--- /dev/null
+++ b/examples/main/default/pages/AnswersHome.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform home page for Answers sites
+ AnswersHome
+
diff --git a/examples/main/default/pages/BandwidthExceeded.page b/examples/main/default/pages/BandwidthExceeded.page
new file mode 100644
index 0000000..c6619ce
--- /dev/null
+++ b/examples/main/default/pages/BandwidthExceeded.page
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/BandwidthExceeded.page-meta.xml b/examples/main/default/pages/BandwidthExceeded.page-meta.xml
new file mode 100644
index 0000000..b216a6f
--- /dev/null
+++ b/examples/main/default/pages/BandwidthExceeded.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform Limit Exceeded page
+ BandwidthExceeded
+
diff --git a/examples/main/default/pages/ChangePassword.page b/examples/main/default/pages/ChangePassword.page
new file mode 100644
index 0000000..ffb6b69
--- /dev/null
+++ b/examples/main/default/pages/ChangePassword.page
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/ChangePassword.page-meta.xml b/examples/main/default/pages/ChangePassword.page-meta.xml
new file mode 100644
index 0000000..416069b
--- /dev/null
+++ b/examples/main/default/pages/ChangePassword.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites Change Password page
+ ChangePassword
+
diff --git a/examples/main/default/pages/CommunitiesLanding.page b/examples/main/default/pages/CommunitiesLanding.page
new file mode 100644
index 0000000..247a950
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesLanding.page
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/CommunitiesLanding.page-meta.xml b/examples/main/default/pages/CommunitiesLanding.page-meta.xml
new file mode 100644
index 0000000..915af51
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesLanding.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default experiences landing page
+ CommunitiesLanding
+
diff --git a/examples/main/default/pages/CommunitiesLogin.page b/examples/main/default/pages/CommunitiesLogin.page
new file mode 100644
index 0000000..ba837c9
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesLogin.page
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/CommunitiesLogin.page-meta.xml b/examples/main/default/pages/CommunitiesLogin.page-meta.xml
new file mode 100644
index 0000000..f339d24
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesLogin.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default experiences login page
+ CommunitiesLogin
+
diff --git a/examples/main/default/pages/CommunitiesSelfReg.page b/examples/main/default/pages/CommunitiesSelfReg.page
new file mode 100644
index 0000000..ba08f09
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesSelfReg.page
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml b/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml
new file mode 100644
index 0000000..b49a43a
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesSelfReg.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default experiences self registration page
+ CommunitiesSelfReg
+
diff --git a/examples/main/default/pages/CommunitiesSelfRegConfirm.page b/examples/main/default/pages/CommunitiesSelfRegConfirm.page
new file mode 100644
index 0000000..cdf230f
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesSelfRegConfirm.page
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!$Label.site.go_to_login_page}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml b/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml
new file mode 100644
index 0000000..f9e3376
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesSelfRegConfirm.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default experiences self registration confirmation page
+ CommunitiesSelfRegConfirm
+
diff --git a/examples/main/default/pages/CommunitiesTemplate.page b/examples/main/default/pages/CommunitiesTemplate.page
new file mode 100644
index 0000000..345cb1d
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesTemplate.page
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/CommunitiesTemplate.page-meta.xml b/examples/main/default/pages/CommunitiesTemplate.page-meta.xml
new file mode 100644
index 0000000..301b18b
--- /dev/null
+++ b/examples/main/default/pages/CommunitiesTemplate.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default template for experiences pages
+ CommunitiesTemplate
+
diff --git a/examples/main/default/pages/Exception.page b/examples/main/default/pages/Exception.page
new file mode 100644
index 0000000..6a1c44f
--- /dev/null
+++ b/examples/main/default/pages/Exception.page
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/Exception.page-meta.xml b/examples/main/default/pages/Exception.page-meta.xml
new file mode 100644
index 0000000..95ba9e2
--- /dev/null
+++ b/examples/main/default/pages/Exception.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform page for post-authentication errors
+ Exception
+
diff --git a/examples/main/default/pages/FileNotFound.page b/examples/main/default/pages/FileNotFound.page
new file mode 100644
index 0000000..de87624
--- /dev/null
+++ b/examples/main/default/pages/FileNotFound.page
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/FileNotFound.page-meta.xml b/examples/main/default/pages/FileNotFound.page-meta.xml
new file mode 100644
index 0000000..6eb6b45
--- /dev/null
+++ b/examples/main/default/pages/FileNotFound.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform Page/Data Not Found page
+ FileNotFound
+
diff --git a/examples/main/default/pages/ForgotPassword.page b/examples/main/default/pages/ForgotPassword.page
new file mode 100644
index 0000000..9877886
--- /dev/null
+++ b/examples/main/default/pages/ForgotPassword.page
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/ForgotPassword.page-meta.xml b/examples/main/default/pages/ForgotPassword.page-meta.xml
new file mode 100644
index 0000000..e20a0c1
--- /dev/null
+++ b/examples/main/default/pages/ForgotPassword.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites Forgot Password Confirmation page
+ ForgotPassword
+
diff --git a/examples/main/default/pages/ForgotPasswordConfirm.page b/examples/main/default/pages/ForgotPasswordConfirm.page
new file mode 100644
index 0000000..77f2654
--- /dev/null
+++ b/examples/main/default/pages/ForgotPasswordConfirm.page
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!$Label.site.go_to_login_page}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml b/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml
new file mode 100644
index 0000000..7240002
--- /dev/null
+++ b/examples/main/default/pages/ForgotPasswordConfirm.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites Forgot Password Confirmation page
+ ForgotPasswordConfirm
+
diff --git a/examples/main/default/pages/IdeasHome.page b/examples/main/default/pages/IdeasHome.page
new file mode 100644
index 0000000..165405b
--- /dev/null
+++ b/examples/main/default/pages/IdeasHome.page
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/main/default/pages/IdeasHome.page-meta.xml b/examples/main/default/pages/IdeasHome.page-meta.xml
new file mode 100644
index 0000000..f3d6d40
--- /dev/null
+++ b/examples/main/default/pages/IdeasHome.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform home page for ideas sites
+ IdeasHome
+
diff --git a/examples/main/default/pages/InMaintenance.page b/examples/main/default/pages/InMaintenance.page
new file mode 100644
index 0000000..a0f5f0f
--- /dev/null
+++ b/examples/main/default/pages/InMaintenance.page
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/InMaintenance.page-meta.xml b/examples/main/default/pages/InMaintenance.page-meta.xml
new file mode 100644
index 0000000..b936fda
--- /dev/null
+++ b/examples/main/default/pages/InMaintenance.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform In Maintenance page
+ InMaintenance
+
diff --git a/examples/main/default/pages/MicrobatchSelfReg.page b/examples/main/default/pages/MicrobatchSelfReg.page
new file mode 100644
index 0000000..96b3069
--- /dev/null
+++ b/examples/main/default/pages/MicrobatchSelfReg.page
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml b/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml
new file mode 100644
index 0000000..25eb82b
--- /dev/null
+++ b/examples/main/default/pages/MicrobatchSelfReg.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Process self-registration requests in batches instead of individually
+ MicrobatchSelfReg
+
diff --git a/examples/main/default/pages/MyProfilePage.page b/examples/main/default/pages/MyProfilePage.page
new file mode 100644
index 0000000..bf72930
--- /dev/null
+++ b/examples/main/default/pages/MyProfilePage.page
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/MyProfilePage.page-meta.xml b/examples/main/default/pages/MyProfilePage.page-meta.xml
new file mode 100644
index 0000000..dc6c56f
--- /dev/null
+++ b/examples/main/default/pages/MyProfilePage.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites My Profile page
+ MyProfilePage
+
diff --git a/examples/main/default/pages/SiteLogin.page b/examples/main/default/pages/SiteLogin.page
new file mode 100644
index 0000000..6f0900b
--- /dev/null
+++ b/examples/main/default/pages/SiteLogin.page
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/SiteLogin.page-meta.xml b/examples/main/default/pages/SiteLogin.page-meta.xml
new file mode 100644
index 0000000..cfb8f57
--- /dev/null
+++ b/examples/main/default/pages/SiteLogin.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites Login page
+ SiteLogin
+
diff --git a/examples/main/default/pages/SiteRegister.page b/examples/main/default/pages/SiteRegister.page
new file mode 100644
index 0000000..b4e443e
--- /dev/null
+++ b/examples/main/default/pages/SiteRegister.page
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/SiteRegister.page-meta.xml b/examples/main/default/pages/SiteRegister.page-meta.xml
new file mode 100644
index 0000000..b01800f
--- /dev/null
+++ b/examples/main/default/pages/SiteRegister.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites User Registration page
+ SiteRegister
+
diff --git a/examples/main/default/pages/SiteRegisterConfirm.page b/examples/main/default/pages/SiteRegisterConfirm.page
new file mode 100644
index 0000000..6001957
--- /dev/null
+++ b/examples/main/default/pages/SiteRegisterConfirm.page
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!$Label.site.go_to_login_page}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml b/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml
new file mode 100644
index 0000000..3fda7c1
--- /dev/null
+++ b/examples/main/default/pages/SiteRegisterConfirm.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Salesforce Sites User Registration Confirmation page
+ SiteRegisterConfirm
+
diff --git a/examples/main/default/pages/SiteTemplate.page b/examples/main/default/pages/SiteTemplate.page
new file mode 100644
index 0000000..2476eb5
--- /dev/null
+++ b/examples/main/default/pages/SiteTemplate.page
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/SiteTemplate.page-meta.xml b/examples/main/default/pages/SiteTemplate.page-meta.xml
new file mode 100644
index 0000000..bbf7815
--- /dev/null
+++ b/examples/main/default/pages/SiteTemplate.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform template for site pages
+ SiteTemplate
+
diff --git a/examples/main/default/pages/StdExceptionTemplate.page b/examples/main/default/pages/StdExceptionTemplate.page
new file mode 100644
index 0000000..965917d
--- /dev/null
+++ b/examples/main/default/pages/StdExceptionTemplate.page
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/StdExceptionTemplate.page-meta.xml b/examples/main/default/pages/StdExceptionTemplate.page-meta.xml
new file mode 100644
index 0000000..ba17804
--- /dev/null
+++ b/examples/main/default/pages/StdExceptionTemplate.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform template for standard exception pages
+ StdExceptionTemplate
+
diff --git a/examples/main/default/pages/Unauthorized.page b/examples/main/default/pages/Unauthorized.page
new file mode 100644
index 0000000..b0e4ff2
--- /dev/null
+++ b/examples/main/default/pages/Unauthorized.page
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/Unauthorized.page-meta.xml b/examples/main/default/pages/Unauthorized.page-meta.xml
new file mode 100644
index 0000000..980b0ed
--- /dev/null
+++ b/examples/main/default/pages/Unauthorized.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform Authorization Required page
+ Unauthorized
+
diff --git a/examples/main/default/pages/UnderConstruction.page b/examples/main/default/pages/UnderConstruction.page
new file mode 100644
index 0000000..81dc16e
--- /dev/null
+++ b/examples/main/default/pages/UnderConstruction.page
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/main/default/pages/UnderConstruction.page-meta.xml b/examples/main/default/pages/UnderConstruction.page-meta.xml
new file mode 100644
index 0000000..301e19a
--- /dev/null
+++ b/examples/main/default/pages/UnderConstruction.page-meta.xml
@@ -0,0 +1,8 @@
+
+
+ 60.0
+ false
+ false
+ Default Lightning Platform Under Construction page
+ UnderConstruction
+
diff --git a/examples/main/default/profiles/Admin.profile-meta.xml b/examples/main/default/profiles/Admin.profile-meta.xml
new file mode 100644
index 0000000..0228dbb
--- /dev/null
+++ b/examples/main/default/profiles/Admin.profile-meta.xml
@@ -0,0 +1,949 @@
+
+
+
+ ChangePasswordController
+ true
+
+
+ ChangePasswordControllerTest
+ true
+
+
+ CommunitiesLandingController
+ true
+
+
+ CommunitiesLandingControllerTest
+ true
+
+
+ CommunitiesLoginController
+ true
+
+
+ CommunitiesLoginControllerTest
+ true
+
+
+ CommunitiesSelfRegConfirmController
+ true
+
+
+ CommunitiesSelfRegConfirmControllerTest
+ true
+
+
+ CommunitiesSelfRegController
+ true
+
+
+ CommunitiesSelfRegControllerTest
+ true
+
+
+ ForgotPasswordController
+ true
+
+
+ ForgotPasswordControllerTest
+ true
+
+
+ LightningForgotPasswordController
+ true
+
+
+ LightningForgotPasswordControllerTest
+ true
+
+
+ LightningLoginFormController
+ true
+
+
+ LightningLoginFormControllerTest
+ true
+
+
+ LightningSelfRegisterController
+ true
+
+
+ LightningSelfRegisterControllerTest
+ true
+
+
+ MicrobatchSelfRegController
+ true
+
+
+ MicrobatchSelfRegControllerTest
+ true
+
+
+ MyProfilePageController
+ true
+
+
+ MyProfilePageControllerTest
+ true
+
+
+ ResourceController
+ true
+
+
+ ShoppingCartController
+ true
+
+
+ SiteLoginController
+ true
+
+
+ SiteLoginControllerTest
+ true
+
+
+ SiteRegisterController
+ true
+
+
+ SiteRegisterControllerTest
+ true
+
+ false
+
+ AnswersHome
+ true
+
+
+ BandwidthExceeded
+ true
+
+
+ ChangePassword
+ true
+
+
+ CommunitiesLanding
+ true
+
+
+ CommunitiesLogin
+ true
+
+
+ CommunitiesSelfReg
+ true
+
+
+ CommunitiesSelfRegConfirm
+ true
+
+
+ CommunitiesTemplate
+ true
+
+
+ Exception
+ true
+
+
+ FileNotFound
+ true
+
+
+ ForgotPassword
+ true
+
+
+ ForgotPasswordConfirm
+ true
+
+
+ IdeasHome
+ true
+
+
+ InMaintenance
+ true
+
+
+ MicrobatchSelfReg
+ true
+
+
+ MyProfilePage
+ true
+
+
+ SiteLogin
+ true
+
+
+ SiteRegister
+ true
+
+
+ SiteRegisterConfirm
+ true
+
+
+ SiteTemplate
+ true
+
+
+ StdExceptionTemplate
+ true
+
+
+ Unauthorized
+ true
+
+
+ UnderConstruction
+ true
+
+ Salesforce
+
+ true
+ AIViewInsightObjects
+
+
+ true
+ ActivateContract
+
+
+ true
+ ActivateOrder
+
+
+ true
+ ActivitiesAccess
+
+
+ true
+ AddDirectMessageMembers
+
+
+ true
+ AllowUniversalSearch
+
+
+ true
+ AllowViewKnowledge
+
+
+ true
+ ApexRestServices
+
+
+ true
+ ApiEnabled
+
+
+ true
+ AssignPermissionSets
+
+
+ true
+ AssignTopics
+
+
+ true
+ AuthorApex
+
+
+ true
+ BulkMacrosAllowed
+
+
+ true
+ CanAccessCE
+
+
+ true
+ CanInsertFeedSystemFields
+
+
+ true
+ CanUseNewDashboardBuilder
+
+
+ true
+ CanVerifyComment
+
+
+ true
+ ChangeDashboardColors
+
+
+ true
+ ChatterEditOwnPost
+
+
+ true
+ ChatterEditOwnRecordPost
+
+
+ true
+ ChatterFileLink
+
+
+ true
+ ChatterInternalUser
+
+
+ true
+ ChatterInviteExternalUsers
+
+
+ true
+ ChatterOwnGroups
+
+
+ true
+ ClientSecretRotation
+
+
+ true
+ ConnectOrgToEnvironmentHub
+
+
+ true
+ ConsentApiUpdate
+
+
+ true
+ ContentAdministrator
+
+
+ true
+ ContentWorkspaces
+
+
+ true
+ ConvertLeads
+
+
+ true
+ CreateCustomizeDashboards
+
+
+ true
+ CreateCustomizeFilters
+
+
+ true
+ CreateCustomizeReports
+
+
+ true
+ CreateDashboardFolders
+
+
+ true
+ CreateLtngTempFolder
+
+
+ true
+ CreateReportFolders
+
+
+ true
+ CreateTopics
+
+
+ true
+ CreateWorkBadgeDefinition
+
+
+ true
+ CreateWorkspaces
+
+
+ true
+ CustomizeApplication
+
+
+ true
+ DataExport
+
+
+ true
+ DelegatedTwoFactor
+
+
+ true
+ DeleteActivatedContract
+
+
+ true
+ DeleteTopics
+
+
+ true
+ DistributeFromPersWksp
+
+
+ true
+ EditActivatedOrders
+
+
+ true
+ EditBillingInfo
+
+
+ true
+ EditBrandTemplates
+
+
+ true
+ EditCaseComments
+
+
+ true
+ EditEvent
+
+
+ true
+ EditHtmlTemplates
+
+
+ true
+ EditKnowledge
+
+
+ true
+ EditMyDashboards
+
+
+ true
+ EditMyReports
+
+
+ true
+ EditOppLineItemUnitPrice
+
+
+ true
+ EditPublicDocuments
+
+
+ true
+ EditPublicFilters
+
+
+ true
+ EditPublicTemplates
+
+
+ true
+ EditReadonlyFields
+
+
+ true
+ EditTask
+
+
+ true
+ EditTopics
+
+
+ true
+ EmailMass
+
+
+ true
+ EmailSingle
+
+
+ true
+ EnableCommunityAppLauncher
+
+
+ true
+ EnableNotifications
+
+
+ true
+ ExportReport
+
+
+ true
+ FieldServiceAccess
+
+
+ true
+ GiveRecognitionBadge
+
+
+ true
+ ImportCustomObjects
+
+
+ true
+ ImportLeads
+
+
+ true
+ ImportPersonal
+
+
+ true
+ InstallPackaging
+
+
+ true
+ LightningConsoleAllowedForUser
+
+
+ true
+ LightningExperienceUser
+
+
+ true
+ ListEmailSend
+
+
+ true
+ ManageAnalyticSnapshots
+
+
+ true
+ ManageAuthProviders
+
+
+ true
+ ManageBusinessHourHolidays
+
+
+ true
+ ManageC360AConnections
+
+
+ true
+ ManageCMS
+
+
+ true
+ ManageCallCenters
+
+
+ true
+ ManageCases
+
+
+ true
+ ManageCategories
+
+
+ true
+ ManageCertificates
+
+
+ true
+ ManageContentPermissions
+
+
+ true
+ ManageContentProperties
+
+
+ true
+ ManageContentTypes
+
+
+ true
+ ManageCustomPermissions
+
+
+ true
+ ManageCustomReportTypes
+
+
+ true
+ ManageDashbdsInPubFolders
+
+
+ true
+ ManageDataCategories
+
+
+ true
+ ManageDataIntegrations
+
+
+ true
+ ManageDataMaskPolicies
+
+
+ true
+ ManageDynamicDashboards
+
+
+ true
+ ManageEmailClientConfig
+
+
+ true
+ ManageEntitlements
+
+
+ true
+ ManageExchangeConfig
+
+
+ true
+ ManageHealthCheck
+
+
+ true
+ ManageHubConnections
+
+
+ true
+ ManageInteraction
+
+
+ true
+ ManageInternalUsers
+
+
+ true
+ ManageIpAddresses
+
+
+ true
+ ManageKnowledge
+
+
+ true
+ ManageKnowledgeImportExport
+
+
+ true
+ ManageLeads
+
+
+ true
+ ManageLoginAccessPolicies
+
+
+ true
+ ManageMobile
+
+
+ true
+ ManageNetworks
+
+
+ true
+ ManageOrchInstsAndWorkItems
+
+
+ true
+ ManagePackageLicenses
+
+
+ true
+ ManagePartners
+
+
+ true
+ ManagePasswordPolicies
+
+
+ true
+ ManageProfilesPermissionsets
+
+
+ true
+ ManagePropositions
+
+
+ true
+ ManagePvtRptsAndDashbds
+
+
+ true
+ ManageRecommendationStrategies
+
+
+ true
+ ManageReleaseUpdates
+
+
+ true
+ ManageRemoteAccess
+
+
+ true
+ ManageReportsInPubFolders
+
+
+ true
+ ManageRoles
+
+
+ true
+ ManageSearchPromotionRules
+
+
+ true
+ ManageSharing
+
+
+ true
+ ManageSolutions
+
+
+ true
+ ManageSubscriptions
+
+
+ true
+ ManageSynonyms
+
+
+ true
+ ManageUnlistedGroups
+
+
+ true
+ ManageUsers
+
+
+ true
+ MassInlineEdit
+
+
+ true
+ MergeTopics
+
+
+ true
+ ModerateChatter
+
+
+ true
+ ModifyAllData
+
+
+ true
+ ModifyDataClassification
+
+
+ true
+ ModifyMetadata
+
+
+ true
+ NewReportBuilder
+
+
+ true
+ OmnichannelInventorySync
+
+
+ true
+ Packaging2
+
+
+ true
+ Packaging2Delete
+
+
+ true
+ PrivacyDataAccess
+
+
+ true
+ RemoveDirectMessageMembers
+
+
+ true
+ ResetPasswords
+
+
+ true
+ RunReports
+
+
+ true
+ ScheduleReports
+
+
+ true
+ SelectFilesFromSalesforce
+
+
+ true
+ SendCustomNotifications
+
+
+ true
+ SendExternalEmailAvailable
+
+
+ true
+ SendSitRequests
+
+
+ true
+ ShareFilesWithNetworks
+
+
+ true
+ ShareInternalArticles
+
+
+ true
+ ShowCompanyNameAsUserBadge
+
+
+ true
+ SolutionImport
+
+
+ true
+ SubmitMacrosAllowed
+
+
+ true
+ SubscribeDashboardRolesGrps
+
+
+ true
+ SubscribeDashboardToOtherUsers
+
+
+ true
+ SubscribeReportRolesGrps
+
+
+ true
+ SubscribeReportToOtherUsers
+
+
+ true
+ SubscribeReportsRunAsUser
+
+
+ true
+ SubscribeToLightningDashboards
+
+
+ true
+ SubscribeToLightningReports
+
+
+ true
+ TransactionalEmailSend
+
+
+ true
+ TransferAnyCase
+
+
+ true
+ TransferAnyEntity
+
+
+ true
+ TransferAnyLead
+
+
+ true
+ UseOmnichannelInventoryAPIs
+
+
+ true
+ UseTeamReassignWizards
+
+
+ true
+ UseWebLink
+
+
+ true
+ ViewAllData
+
+
+ true
+ ViewAllProfiles
+
+
+ true
+ ViewAllUsers
+
+
+ true
+ ViewDataAssessment
+
+
+ true
+ ViewDataCategories
+
+
+ true
+ ViewDataLeakageEvents
+
+
+ true
+ ViewDeveloperName
+
+
+ true
+ ViewEventLogFiles
+
+
+ true
+ ViewFlowUsageAndFlowEventData
+
+
+ true
+ ViewHealthCheck
+
+
+ true
+ ViewHelpLink
+
+
+ true
+ ViewMLModels
+
+
+ true
+ ViewMyTeamsDashboards
+
+
+ true
+ ViewPlatformEvents
+
+
+ true
+ ViewPublicDashboards
+
+
+ true
+ ViewPublicReports
+
+
+ true
+ ViewRoles
+
+
+ true
+ ViewSetup
+
+
+ true
+ ViewUserPII
+
+
+ true
+ WorkCalibrationUser
+
+
diff --git a/examples/main/default/profiles/examples Profile.profile-meta.xml b/examples/main/default/profiles/examples Profile.profile-meta.xml
new file mode 100644
index 0000000..4581d68
--- /dev/null
+++ b/examples/main/default/profiles/examples Profile.profile-meta.xml
@@ -0,0 +1,209 @@
+
+
+
+ ChangePasswordController
+ false
+
+
+ ChangePasswordControllerTest
+ false
+
+
+ CommunitiesLandingController
+ false
+
+
+ CommunitiesLandingControllerTest
+ false
+
+
+ CommunitiesLoginController
+ false
+
+
+ CommunitiesLoginControllerTest
+ false
+
+
+ CommunitiesSelfRegConfirmController
+ false
+
+
+ CommunitiesSelfRegConfirmControllerTest
+ false
+
+
+ CommunitiesSelfRegController
+ false
+
+
+ CommunitiesSelfRegControllerTest
+ false
+
+
+ ForgotPasswordController
+ false
+
+
+ ForgotPasswordControllerTest
+ false
+
+
+ LightningForgotPasswordController
+ false
+
+
+ LightningForgotPasswordControllerTest
+ false
+
+
+ LightningLoginFormController
+ false
+
+
+ LightningLoginFormControllerTest
+ false
+
+
+ LightningSelfRegisterController
+ false
+
+
+ LightningSelfRegisterControllerTest
+ false
+
+
+ MicrobatchSelfRegController
+ false
+
+
+ MicrobatchSelfRegControllerTest
+ false
+
+
+ MyProfilePageController
+ false
+
+
+ MyProfilePageControllerTest
+ false
+
+
+ ResourceController
+ false
+
+
+ ShoppingCartController
+ false
+
+
+ SiteLoginController
+ false
+
+
+ SiteLoginControllerTest
+ false
+
+
+ SiteRegisterController
+ false
+
+
+ SiteRegisterControllerTest
+ false
+
+ true
+
+ AnswersHome
+ false
+
+
+ BandwidthExceeded
+ true
+
+
+ ChangePassword
+ false
+
+
+ CommunitiesLanding
+ true
+
+
+ CommunitiesLogin
+ true
+
+
+ CommunitiesSelfReg
+ true
+
+
+ CommunitiesSelfRegConfirm
+ true
+
+
+ CommunitiesTemplate
+ true
+
+
+ Exception
+ true
+
+
+ FileNotFound
+ true
+
+
+ ForgotPassword
+ true
+
+
+ ForgotPasswordConfirm
+ true
+
+
+ IdeasHome
+ false
+
+
+ InMaintenance
+ true
+
+
+ MicrobatchSelfReg
+ true
+
+
+ MyProfilePage
+ false
+
+
+ SiteLogin
+ true
+
+
+ SiteRegister
+ true
+
+
+ SiteRegisterConfirm
+ true
+
+
+ SiteTemplate
+ false
+
+
+ StdExceptionTemplate
+ false
+
+
+ Unauthorized
+ false
+
+
+ UnderConstruction
+ true
+
+ Guest User License
+
diff --git a/examples/main/default/sites/examples.site-meta.xml b/examples/main/default/sites/examples.site-meta.xml
new file mode 100644
index 0000000..3dfab52
--- /dev/null
+++ b/examples/main/default/sites/examples.site-meta.xml
@@ -0,0 +1,30 @@
+
+
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ CommunitiesLogin
+ BandwidthExceeded
+ true
+ true
+ SameOriginOnly
+ true
+ true
+ FileNotFound
+ Exception
+ InMaintenance
+ CommunitiesLanding
+ examples
+ false
+ true
+ CommunitiesSelfReg
+ test-aihaub8gzvvn@example.com
+ test-aihaub8gzvvn@example.com
+ ChatterNetwork
+ vforcesite
+
diff --git a/examples/main/default/staticresources/SiteSamples.resource-meta.xml b/examples/main/default/staticresources/SiteSamples.resource-meta.xml
new file mode 100644
index 0000000..1ed30e1
--- /dev/null
+++ b/examples/main/default/staticresources/SiteSamples.resource-meta.xml
@@ -0,0 +1,6 @@
+
+
+ Public
+ application/zip
+ Static resource for sites sample pages
+
diff --git a/examples/main/default/staticresources/SiteSamples/SiteStyles.css b/examples/main/default/staticresources/SiteSamples/SiteStyles.css
new file mode 100644
index 0000000..c61cbc0
--- /dev/null
+++ b/examples/main/default/staticresources/SiteSamples/SiteStyles.css
@@ -0,0 +1,28 @@
+.topPanelContainer {
+ text-align:left;
+ border:1px solid #ccc;
+}
+
+.topPanel {
+ background-color: white;
+ border: 1px solid #ccc;
+ padding: 0px;
+ margin-top: 10px;
+ margin-bottom: 0px;
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.title {
+ font-size: larger;
+ font-weight: bold;
+}
+
+.poweredByImage {
+ vertical-align: middle;
+ margin:12px 8px 8px 0;
+}
+
+img {
+ border: none;
+}
\ No newline at end of file
diff --git a/examples/main/default/staticresources/SiteSamples/img/clock.png b/examples/main/default/staticresources/SiteSamples/img/clock.png
new file mode 100644
index 0000000..17d6749
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/clock.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/construction.png b/examples/main/default/staticresources/SiteSamples/img/construction.png
new file mode 100644
index 0000000..236ead9
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/construction.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/force_logo.png b/examples/main/default/staticresources/SiteSamples/img/force_logo.png
new file mode 100644
index 0000000..6ba12db
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/force_logo.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/maintenance.png b/examples/main/default/staticresources/SiteSamples/img/maintenance.png
new file mode 100644
index 0000000..45b636a
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/maintenance.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/poweredby.png b/examples/main/default/staticresources/SiteSamples/img/poweredby.png
new file mode 100644
index 0000000..b2b4f28
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/poweredby.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/tools.png b/examples/main/default/staticresources/SiteSamples/img/tools.png
new file mode 100644
index 0000000..44a3ee1
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/tools.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/unauthorized.png b/examples/main/default/staticresources/SiteSamples/img/unauthorized.png
new file mode 100644
index 0000000..1b93e9b
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/unauthorized.png differ
diff --git a/examples/main/default/staticresources/SiteSamples/img/warning.png b/examples/main/default/staticresources/SiteSamples/img/warning.png
new file mode 100644
index 0000000..325ac99
Binary files /dev/null and b/examples/main/default/staticresources/SiteSamples/img/warning.png differ
diff --git a/examples/main/default/staticresources/tw.resource-meta.xml b/examples/main/default/staticresources/tw.resource-meta.xml
new file mode 100644
index 0000000..54184e4
--- /dev/null
+++ b/examples/main/default/staticresources/tw.resource-meta.xml
@@ -0,0 +1,6 @@
+
+
+ Private
+ application/zip
+ tw
+
diff --git a/examples/main/default/staticresources/tw/css/main.css b/examples/main/default/staticresources/tw/css/main.css
new file mode 100644
index 0000000..a4cf594
--- /dev/null
+++ b/examples/main/default/staticresources/tw/css/main.css
@@ -0,0 +1 @@
+/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border-width:0!important}.pointer-events-none{pointer-events:none!important}.pointer-events-auto{pointer-events:auto!important}.fixed{position:fixed!important}.relative{position:relative!important}.inset-0{inset:0!important}.-my-4{margin-top:-1rem!important;margin-bottom:-1rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.ml-3{margin-left:.75rem!important}.ml-4{margin-left:1rem!important}.mt-1{margin-top:.25rem!important}.mt-10{margin-top:2.5rem!important}.mt-2{margin-top:.5rem!important}.mt-4{margin-top:1rem!important}.mt-6{margin-top:1.5rem!important}.block{display:block!important}.flex{display:flex!important}.inline-flex{display:inline-flex!important}.flow-root{display:flow-root!important}.h-12{height:3rem!important}.h-24{height:6rem!important}.h-5{height:1.25rem!important}.w-0{width:0!important}.w-12{width:3rem!important}.w-24{width:6rem!important}.w-5{width:1.25rem!important}.w-full{width:100%!important}.max-w-4xl{max-width:56rem!important}.max-w-sm{max-width:24rem!important}.flex-1{flex:1 1 0%!important}.flex-shrink-0{flex-shrink:0!important}.translate-y-0{--tw-translate-y:0px!important}.translate-y-0,.translate-y-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-2{--tw-translate-y:0.5rem!important}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.flex-col{flex-direction:column!important}.items-end{align-items:flex-end!important}.items-center{align-items:center!important}.justify-center{justify-content:center!important}.justify-between{justify-content:space-between!important}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0!important;margin-right:calc(.5rem*var(--tw-space-x-reverse))!important;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))!important}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))!important;margin-bottom:calc(1rem*var(--tw-space-y-reverse))!important}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0!important;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))!important;border-bottom-width:calc(1px*var(--tw-divide-y-reverse))!important}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1!important;border-color:rgb(229 231 235/var(--tw-divide-opacity))!important}.overflow-hidden{overflow:hidden!important}.rounded-lg{border-radius:.5rem!important}.rounded-md{border-radius:.375rem!important}.border{border-width:1px!important}.border-2{border-width:2px!important}.border-b{border-bottom-width:1px!important}.border-t{border-top-width:1px!important}.border-dashed{border-style:dashed!important}.border-gray-200{--tw-border-opacity:1!important;border-color:rgb(229 231 235/var(--tw-border-opacity))!important}.border-gray-300{--tw-border-opacity:1!important;border-color:rgb(209 213 219/var(--tw-border-opacity))!important}.border-transparent{border-color:#0000!important}.bg-gray-50{--tw-bg-opacity:1!important;background-color:rgb(249 250 251/var(--tw-bg-opacity))!important}.bg-indigo-600{background-color:rgb(79 70 229/var(--tw-bg-opacity))!important}.bg-indigo-600,.bg-white{--tw-bg-opacity:1!important}.bg-white{background-color:rgb(255 255 255/var(--tw-bg-opacity))!important}.object-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-center{-o-object-position:center!important;object-position:center!important}.p-12{padding:3rem!important}.p-4{padding:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.py-6{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.pb-16{padding-bottom:4rem!important}.pr-6{padding-right:1.5rem!important}.text-center{text-align:center!important}.text-right{text-align:right!important}.text-3xl{font-size:1.875rem!important;line-height:2.25rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-sm{font-size:.875rem!important;line-height:1.25rem!important}.font-bold{font-weight:700!important}.font-medium{font-weight:500!important}.font-semibold{font-weight:600!important}.tracking-tight{letter-spacing:-.025em!important}.text-gray-400{--tw-text-opacity:1!important;color:rgb(156 163 175/var(--tw-text-opacity))!important}.text-gray-500{--tw-text-opacity:1!important;color:rgb(107 114 128/var(--tw-text-opacity))!important}.text-gray-600{--tw-text-opacity:1!important;color:rgb(75 85 99/var(--tw-text-opacity))!important}.text-gray-700{--tw-text-opacity:1!important;color:rgb(55 65 81/var(--tw-text-opacity))!important}.text-gray-900{--tw-text-opacity:1!important;color:rgb(17 24 39/var(--tw-text-opacity))!important}.text-green-500{--tw-text-opacity:1!important;color:rgb(34 197 94/var(--tw-text-opacity))!important}.text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.text-white{--tw-text-opacity:1!important;color:rgb(255 255 255/var(--tw-text-opacity))!important}.opacity-0{opacity:0!important}.opacity-100{opacity:1!important}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a!important;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)!important}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d!important;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)!important}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important}.ring-black{--tw-ring-opacity:1!important;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))!important}.ring-opacity-5{--tw-ring-opacity:0.05!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important;transition-duration:.15s!important}.duration-100{transition-duration:.1s!important}.duration-300{transition-duration:.3s!important}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)!important}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)!important}.hover\:border-gray-400:hover{--tw-border-opacity:1!important;border-color:rgb(156 163 175/var(--tw-border-opacity))!important}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1!important;background-color:rgb(67 56 202/var(--tw-bg-opacity))!important}.hover\:text-gray-500:hover{--tw-text-opacity:1!important;color:rgb(107 114 128/var(--tw-text-opacity))!important}.hover\:text-gray-800:hover{--tw-text-opacity:1!important;color:rgb(31 41 55/var(--tw-text-opacity))!important}.hover\:text-indigo-500:hover{--tw-text-opacity:1!important;color:rgb(99 102 241/var(--tw-text-opacity))!important}.focus\:outline-none:focus{outline:2px solid #0000!important;outline-offset:2px!important}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)!important;--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)!important;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)!important}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1!important;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))!important}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px!important}.focus\:ring-offset-gray-50:focus{--tw-ring-offset-color:#f9fafb!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}@media (min-width:640px){.sm\:absolute{position:absolute!important}.sm\:left-1\/2{left:50%!important}.sm\:top-0{top:0!important}.sm\:ml-0{margin-left:0!important}.sm\:ml-6{margin-left:1.5rem!important}.sm\:mt-0{margin-top:0!important}.sm\:mt-3{margin-top:.75rem!important}.sm\:block{display:block!important}.sm\:grid{display:grid!important}.sm\:h-32{height:8rem!important}.sm\:w-32{width:8rem!important}.sm\:translate-x-0{--tw-translate-x:0px!important}.sm\:translate-x-0,.sm\:translate-x-2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:translate-x-2{--tw-translate-x:0.5rem!important}.sm\:translate-y-0{--tw-translate-y:0px!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:items-start{align-items:flex-start!important}.sm\:items-end{align-items:flex-end!important}.sm\:p-6{padding:1.5rem!important}.sm\:py-10{padding-top:2.5rem!important;padding-bottom:2.5rem!important}}@media (min-width:1024px){.lg\:p-8{padding:2rem!important}}
\ No newline at end of file
diff --git a/examples/server-communication/classes/ResourceController.cls b/examples/server-communication/classes/ResourceController.cls
index dbe1323..1694193 100644
--- a/examples/server-communication/classes/ResourceController.cls
+++ b/examples/server-communication/classes/ResourceController.cls
@@ -14,6 +14,7 @@ public without sharing class ResourceController {
@AuraEnabled(Cacheable=true)
public static List getContacts() {
+ System.debug('getContacts() called');
return [SELECT Id, Name FROM Contact];
}
}
diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html
index c73d5d6..9653fa3 100644
--- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html
+++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.html
@@ -14,4 +14,4 @@ Selected Account
-
+
\ No newline at end of file
diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js
index c2a38ce..665543c 100644
--- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js
+++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js
@@ -4,4 +4,4 @@ import { getAccount } from "c/demoSignals";
export default class DisplaySelectedAccount extends LightningElement {
account = $computed(() => (this.account = getAccount.value)).value;
-}
+}
\ No newline at end of file
diff --git a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml
index 7899a89..b2680f6 100644
--- a/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml
+++ b/examples/server-communication/lwc/displaySelectedAccount/displaySelectedAccount.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.html b/examples/server-communication/lwc/listAccounts/listAccounts.html
index 18f66f8..78d93b7 100644
--- a/examples/server-communication/lwc/listAccounts/listAccounts.html
+++ b/examples/server-communication/lwc/listAccounts/listAccounts.html
@@ -6,4 +6,4 @@
options={accounts}
onchange={handleAccountChange}
>
-
+
\ No newline at end of file
diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.js b/examples/server-communication/lwc/listAccounts/listAccounts.js
index a885e4e..eedb476 100644
--- a/examples/server-communication/lwc/listAccounts/listAccounts.js
+++ b/examples/server-communication/lwc/listAccounts/listAccounts.js
@@ -28,4 +28,4 @@ export default class ListAccounts extends LightningElement {
handleAccountChange(event) {
selectedAccountId.value = event.detail.value;
}
-}
+}
\ No newline at end of file
diff --git a/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml b/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml
index 147d9cb..64e404d 100644
--- a/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml
+++ b/examples/server-communication/lwc/listAccounts/listAccounts.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.html b/examples/server-communication/lwc/serverFetcher/serverFetcher.html
index f60a6c2..4b4d549 100644
--- a/examples/server-communication/lwc/serverFetcher/serverFetcher.html
+++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.html
@@ -10,4 +10,4 @@
-
+
\ No newline at end of file
diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.js b/examples/server-communication/lwc/serverFetcher/serverFetcher.js
index 3ed29d6..4c2eb48 100644
--- a/examples/server-communication/lwc/serverFetcher/serverFetcher.js
+++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.js
@@ -4,4 +4,4 @@ import { fetchContacts } from "c/demoSignals";
export default class ServerFetcher extends LightningElement {
contacts = $computed(() => (this.contacts = fetchContacts.value)).value;
-}
+}
\ No newline at end of file
diff --git a/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml b/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml
index c7b87e6..9866124 100644
--- a/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml
+++ b/examples/server-communication/lwc/serverFetcher/serverFetcher.js-meta.xml
@@ -8,4 +8,4 @@
lightningCommunity__Default
lightningCommunity__Page
-
+
\ No newline at end of file
diff --git a/examples/shopping-cart/controllers/ShoppingCartController.cls b/examples/shopping-cart/controllers/ShoppingCartController.cls
new file mode 100644
index 0000000..327688b
--- /dev/null
+++ b/examples/shopping-cart/controllers/ShoppingCartController.cls
@@ -0,0 +1,50 @@
+public with sharing class ShoppingCartController {
+ @AuraEnabled(Cacheable=true)
+ public static ShoppingCartDto getShoppingCart() {
+ return new ShoppingCartDto(buildSampleItemList());
+ }
+
+ @AuraEnabled
+ public static ShoppingCartDto updateShoppingCart(List newItems) {
+ // This is just for example purpose, in a real-world scenario, you would update the shopping cart in the database
+ return new ShoppingCartDto(newItems);
+ }
+
+ private static List buildSampleItemList() {
+ List items = new List();
+ items.add(new ItemDto('1', 'Nomad Tumbler', new List{'White'}, 1, 35.0, 'https://tailwindui.com/img/ecommerce-images/shopping-cart-page-01-product-03.jpg'));
+ items.add(new ItemDto('2', 'Basic Tee', new List{'Sienna', 'Large'}, 1, 32.0, 'https://tailwindui.com/img/ecommerce-images/shopping-cart-page-01-product-01.jpg'));
+ return items;
+ }
+
+ private static final Decimal TAX_RATE = 0.1;
+ public class ItemDto {
+ @AuraEnabled public String id { get; set; }
+ @AuraEnabled public String name { get; set; }
+ @AuraEnabled public List properties { get; set; }
+ @AuraEnabled public Integer quantity { get; set; }
+ @AuraEnabled public Decimal price { get; set; }
+ @AuraEnabled public Decimal taxAmount { get {return (price * quantity) * TAX_RATE;} }
+ @AuraEnabled public String imgUrl { get; set; }
+
+ // No arg constructor so it can be built from JSON
+ public ItemDto() {}
+
+ public ItemDto(String id, String name, List properties, Integer quantity, Decimal price, String imgUrl) {
+ this.id = id;
+ this.name = name;
+ this.properties = properties;
+ this.quantity = quantity;
+ this.price = price;
+ this.imgUrl = imgUrl;
+ }
+ }
+
+ public class ShoppingCartDto {
+ @AuraEnabled public List items;
+
+ public ShoppingCartDto(List items) {
+ this.items = items;
+ }
+ }
+}
diff --git a/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml b/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml
new file mode 100644
index 0000000..f5e18fd
--- /dev/null
+++ b/examples/shopping-cart/controllers/ShoppingCartController.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 60.0
+ Active
+
diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html
new file mode 100644
index 0000000..41a40c8
--- /dev/null
+++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js
new file mode 100644
index 0000000..209f871
--- /dev/null
+++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js
@@ -0,0 +1,19 @@
+import TwElement from "c/twElement";
+import {$computed} from 'c/signals';
+import {shoppingCart} from "c/demoSignals";
+
+// States
+import ready from "./states/ready.html";
+import loading from "./states/loading.html";
+
+export default class CheckoutButton extends TwElement {
+ itemData = $computed(() => this.itemData = shoppingCart.value).value;
+
+ render() {
+ return this.itemData.loading ? loading : ready;
+ }
+
+ get isEmpty() {
+ return this.itemData.data.items.length === 0;
+ }
+}
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml
new file mode 100644
index 0000000..ec3011f
--- /dev/null
+++ b/examples/shopping-cart/lwc/checkoutButton/checkoutButton.js-meta.xml
@@ -0,0 +1,11 @@
+
+
+ 60.0
+ Checkout Button
+ true
+ Checkout Button
+
+ lightningCommunity__Default
+ lightningCommunity__Page
+
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/checkoutButton/states/loading.html b/examples/shopping-cart/lwc/checkoutButton/states/loading.html
new file mode 100644
index 0000000..c985c2f
--- /dev/null
+++ b/examples/shopping-cart/lwc/checkoutButton/states/loading.html
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/checkoutButton/states/ready.html b/examples/shopping-cart/lwc/checkoutButton/states/ready.html
new file mode 100644
index 0000000..81b4d39
--- /dev/null
+++ b/examples/shopping-cart/lwc/checkoutButton/states/ready.html
@@ -0,0 +1,24 @@
+
+
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.html b/examples/shopping-cart/lwc/orderSummary/orderSummary.html
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.html
@@ -0,0 +1 @@
+
diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.js b/examples/shopping-cart/lwc/orderSummary/orderSummary.js
new file mode 100644
index 0000000..9f8bb92
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.js
@@ -0,0 +1,43 @@
+import TwElement from "c/twElement";
+import { $computed } from "c/signals";
+import { shoppingCart } from "c/demoSignals";
+
+// States
+import ready from "./states/ready.html";
+import loading from "./states/loading.html";
+import empty from "./states/empty.html";
+
+export default class OrderSummary extends TwElement {
+ itemData = $computed(() => (this.itemData = shoppingCart.value)).value;
+
+ render() {
+ return this.itemData.loading
+ ? loading
+ : this.items.length > 0
+ ? ready
+ : empty;
+ }
+
+ get items() {
+ return this.itemData.data?.items ?? [];
+ }
+
+ get subtotal() {
+ return this.items.reduce(
+ (acc, item) => acc + item.price * item.quantity,
+ 0
+ );
+ }
+
+ get tax() {
+ return this.items.reduce((acc, item) => acc + item.taxAmount, 0);
+ }
+
+ get shipping() {
+ return this.items.length > 0 ? 10 : 0;
+ }
+
+ get total() {
+ return this.subtotal + this.tax + this.shipping;
+ }
+}
diff --git a/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml b/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml
new file mode 100644
index 0000000..55d0827
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/orderSummary.js-meta.xml
@@ -0,0 +1,11 @@
+
+
+ 60.0
+ Order Summary
+ true
+ Order Summary
+
+ lightningCommunity__Default
+ lightningCommunity__Page
+
+
diff --git a/examples/shopping-cart/lwc/orderSummary/states/empty.html b/examples/shopping-cart/lwc/orderSummary/states/empty.html
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/states/empty.html
@@ -0,0 +1 @@
+
diff --git a/examples/shopping-cart/lwc/orderSummary/states/loading.html b/examples/shopping-cart/lwc/orderSummary/states/loading.html
new file mode 100644
index 0000000..cf7bd88
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/states/loading.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/examples/shopping-cart/lwc/orderSummary/states/ready.html b/examples/shopping-cart/lwc/orderSummary/states/ready.html
new file mode 100644
index 0000000..c900295
--- /dev/null
+++ b/examples/shopping-cart/lwc/orderSummary/states/ready.html
@@ -0,0 +1,54 @@
+
+
+
+
+
Order summary
+
+
+
+
+
Subtotal
+
+
+
+
+
+
Shipping
+
+
+
+
+
+
Tax
+
+
+
+
+
+
Order total
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.html
@@ -0,0 +1 @@
+
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js
new file mode 100644
index 0000000..adbd133
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js
@@ -0,0 +1,78 @@
+import TwElement from "c/twElement";
+import { $computed } from "c/signals";
+import {
+ shoppingCart,
+ updateCart,
+ cartHistory,
+ undoCartChange
+} from "c/demoSignals";
+
+// States
+import ready from "./states/ready.html";
+import loading from "./states/loading.html";
+import empty from "./states/empty.html";
+
+export default class ShoppingCartDetails extends TwElement {
+ itemData = $computed(() => (this.itemData = shoppingCart.value)).value;
+ cartHistoryLength = $computed(
+ () => (this.cartHistoryLength = cartHistory.value.length)
+ ).value;
+
+ connectedCallback() {
+ super.connectedCallback();
+ }
+
+ render() {
+ return this.itemData.loading
+ ? loading
+ : this.items.length > 0
+ ? ready
+ : empty;
+ }
+
+ get quantityOptions() {
+ return [
+ { label: "1", value: 1 },
+ { label: "2", value: 2 },
+ { label: "3", value: 3 },
+ { label: "4", value: 4 },
+ { label: "5", value: 5 }
+ ];
+ }
+
+ get items() {
+ return (
+ this.itemData.data.items?.map((item) => {
+ return { ...item, total: item.price * item.quantity };
+ }) ?? []
+ );
+ }
+
+ removeItem(event) {
+ event.preventDefault();
+ const itemId = event.target.dataset.item;
+ updateCart({
+ items: this.items.filter((item) => item.id !== itemId)
+ });
+ }
+
+ handleQuantityChange(event) {
+ const itemId = event.target.dataset.item;
+ const newQuantity = event.target.value;
+ const newItems = this.items.map((item) => {
+ if (item.id === itemId) {
+ return { ...item, quantity: newQuantity };
+ }
+ return item;
+ });
+ updateCart({ items: newItems });
+ }
+
+ get displayUndoPanel() {
+ return this.cartHistoryLength > 0;
+ }
+
+ undo() {
+ undoCartChange();
+ }
+}
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml
new file mode 100644
index 0000000..5e20735
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/shoppingCartDetails.js-meta.xml
@@ -0,0 +1,11 @@
+
+
+ 60.0
+ Shopping Cart Details
+ true
+ Shopping Cart Details
+
+ lightningCommunity__Default
+ lightningCommunity__Page
+
+
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html
new file mode 100644
index 0000000..c28488f
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/empty.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+ You have nothing in your shopping cart.
+
+
+
+
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html
new file mode 100644
index 0000000..6309482
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/loading.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html b/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html
new file mode 100644
index 0000000..98e77ff
--- /dev/null
+++ b/examples/shopping-cart/lwc/shoppingCartDetails/states/ready.html
@@ -0,0 +1,140 @@
+
+
+
+ Shopping Cart
+
+
+
Items in your shopping cart
+
+
+
+
+
+
+
+
+
+
+
+
+
+ In stock
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Changes have been made to the cart
+
+
+ Undo
+
+
+
+
+
+
+
+
+
diff --git a/examples/shopping-cart/lwc/stencil/stencil.css b/examples/shopping-cart/lwc/stencil/stencil.css
new file mode 100644
index 0000000..c48a6f7
--- /dev/null
+++ b/examples/shopping-cart/lwc/stencil/stencil.css
@@ -0,0 +1,30 @@
+.container {
+ overflow: hidden;
+ border-radius: 0.25rem;
+}
+
+.loading {
+ margin: 0;
+ padding: 0;
+ height: inherit;
+ overflow: hidden;
+ position: relative;
+ background-color: #e2e2e2;
+}
+
+.loading::after {
+ display: block;
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ transform: translateX(-100%);
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .2), transparent);
+ animation: loading 1.5s infinite;
+}
+
+@keyframes loading {
+ 100% {
+ transform: translateX(100%);
+ }
+}
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/stencil/stencil.html b/examples/shopping-cart/lwc/stencil/stencil.html
new file mode 100644
index 0000000..c2bc2c9
--- /dev/null
+++ b/examples/shopping-cart/lwc/stencil/stencil.html
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/stencil/stencil.js b/examples/shopping-cart/lwc/stencil/stencil.js
new file mode 100644
index 0000000..51ef137
--- /dev/null
+++ b/examples/shopping-cart/lwc/stencil/stencil.js
@@ -0,0 +1,94 @@
+import { LightningElement, api } from 'lwc';
+
+/**
+ * Stencil class for `c-stencil` component.
+ * @extends {LightningElement}
+ */
+export default class Stencil extends LightningElement {
+ /**
+ * The height to set on the stencil to rendered.
+ * @type {number}
+ * @access public
+ * @default 10
+ */
+ @api height = 10;
+
+ /**
+ * The width to set on the stencil to render.
+ * @type {number}
+ * @access public
+ */
+ @api width;
+
+ /**
+ * Whether the stencil should render with a circular shape.
+ * @type {boolean}
+ * @access public
+ * @default false
+ */
+ @api circle = false;
+
+ /**
+ * The amount of stencil containers to create.
+ * @type {number}
+ * @access public
+ * @default 1
+ */
+ @api count = 1;
+
+ /**
+ * The shade in which the stencil will be rendered.
+ * Possible values are `light`, `medium` or `dark`.
+ * @type {string}
+ * @access public
+ * @default medium
+ */
+ @api weightVariant = 'medium';
+
+ get containerStyle() {
+ return `${this.containerHeight}; ${this.containerWidth}; ${this.containerRadius}`;
+ }
+
+ get containerHeight() {
+ return `height: ${this.height}px`;
+ }
+
+ get containerWidth() {
+ if (!this.width) {
+ return 'width: 100%';
+ }
+
+ return `width: ${this.width}px`;
+ }
+
+ get containerRadius() {
+ if (!this.circle) {
+ return 'border-radius: 0.25rem';
+ }
+
+ return 'border-radius: 50%';
+ }
+
+ get items() {
+ let itemArray = [];
+ for (let i = 0; i < this.count; i++) {
+ itemArray.push(i.toString());
+ }
+
+ return itemArray;
+ }
+
+ get loadingBackgroundColor() {
+ if (this.weightVariant === 'light') {
+ return 'background-color: #f3f2f2';
+ }
+
+ if (this.weightVariant === 'medium') {
+ return 'background-color: #e2e2e2';
+ }
+
+ if (this.weightVariant === 'dark') {
+ return 'background-color: #ccc';
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml b/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml
new file mode 100644
index 0000000..ec9b323
--- /dev/null
+++ b/examples/shopping-cart/lwc/stencil/stencil.js-meta.xml
@@ -0,0 +1,7 @@
+
+
+ 60.0
+ Stencil
+ false
+ Stencil
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/twElement/twElement.html b/examples/shopping-cart/lwc/twElement/twElement.html
new file mode 100644
index 0000000..c77e9ec
--- /dev/null
+++ b/examples/shopping-cart/lwc/twElement/twElement.html
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/twElement/twElement.js b/examples/shopping-cart/lwc/twElement/twElement.js
new file mode 100644
index 0000000..36d2bf8
--- /dev/null
+++ b/examples/shopping-cart/lwc/twElement/twElement.js
@@ -0,0 +1,13 @@
+import {LightningElement} from 'lwc';
+import { loadStyle } from "lightning/platformResourceLoader";
+
+import tw from '@salesforce/resourceUrl/tw';
+
+export default class TwElement extends LightningElement {
+ connectedCallback() {
+ this.template.host.style.opacity = "0";
+ loadStyle(this, `${tw}/css/main.css`).then(() => {
+ this.template.host.style.opacity = "1";
+ });
+ }
+}
\ No newline at end of file
diff --git a/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml b/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml
new file mode 100644
index 0000000..09c3d7b
--- /dev/null
+++ b/examples/shopping-cart/lwc/twElement/twElement.js-meta.xml
@@ -0,0 +1,7 @@
+
+
+ 60.0
+ Tw Element
+ false
+ Tw Element
+
\ No newline at end of file
diff --git a/examples/tw/input.css b/examples/tw/input.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/examples/tw/input.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/force-app/lwc/signals/core.js b/force-app/lwc/signals/core.js
index 93a414b..87f5596 100644
--- a/force-app/lwc/signals/core.js
+++ b/force-app/lwc/signals/core.js
@@ -3,6 +3,25 @@ const context = [];
function _getCurrentObserver() {
return context[context.length - 1];
}
+/**
+ * Creates a new effect that will be executed immediately and whenever
+ * any of the signals it reads from change.
+ *
+ * Avoid changing $signal values inside an effect, as it can lead to
+ * infinite loops.
+ *
+ * ```javascript
+ * import { $signal, $effect } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * $effect(() => {
+ * console.log(count.value);
+ * });
+ * ```
+ *
+ * @param fn The function to execute
+ */
function $effect(fn) {
const execute = () => {
context.push(execute);
@@ -14,13 +33,53 @@ function $effect(fn) {
};
execute();
}
+/**
+ * Creates a new computed value that will be updated whenever the signals
+ * it reads from change. Returns a read-only signal that contains the
+ * computed value.
+ *
+ * ```javascript
+ * import { $signal, $computed } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * const double = $computed(() => count.value * 2);
+ * ```
+ *
+ * @param fn The function that returns the computed value.
+ */
function $computed(fn) {
- const computedSignal = $signal(fn());
+ // The initial value is undefined, as it will be computed
+ // when the effect runs for the first time
+ const computedSignal = $signal(undefined);
$effect(() => {
computedSignal.value = fn();
});
return computedSignal.readOnly;
}
+/**
+ * Creates a new signal with the provided value. A signal is a reactive
+ * primitive that can be used to store and update values. Signals can be
+ * read and written to, and can be used to create computed values or
+ * can be read from within an effect.
+ *
+ * You can read the current value of a signal by accessing the `value` property.
+ *
+ * ```javascript
+ * import { $signal } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * // Read the current value, logs 0
+ * console.log(count.value);
+ *
+ * // Update the value
+ * count.value = 1;
+ * ```
+ *
+ * @param value The initial value of the signal
+ * @param options Options to configure the signal
+ */
function $signal(
value,
options = {
@@ -37,7 +96,7 @@ function $signal(
return _storageOption.get();
}
function setter(newValue) {
- if (newValue === _storageOption) {
+ if (newValue === _storageOption.get()) {
return;
}
_storageOption.set(newValue);
@@ -65,6 +124,54 @@ function $signal(
delete returnValue.set;
return returnValue;
}
+/**
+ * Creates a new resource that fetches data from an async source. The resource
+ * will automatically fetch the data when the component is mounted.
+ *
+ * It receives a function that returns a promise, which will be called to fetch
+ * the data. Optionally, you can provide a source object or function that will
+ * be used as the parameters for the fetch function.
+ *
+ * If a function that contain $computed values is provided as the source, the
+ * resource will automatically refetch the data when the computed value changes.
+ *
+ * `$resource` returns an object with 2 properties:
+ * - `data`: a signal that contains the current state of the resource. It has
+ * the following shape:
+ * ```javascript
+ * {
+ * data: T | null;
+ * loading: boolean;
+ * error: unknown | null;
+ * }
+ * ```
+ *
+ * - `refetch`: a function that can be called to force refetch the data.
+ *
+ * ```javascript
+ * import { $signal, $resource } from 'c/signals';
+ * import getAccounts from '@salesforce/apex/AccountController.getAccounts';
+ *
+ * const accountId = $signal('00B5e00000Dv9ZCEAZ');
+ *
+ * // If the account Id value is changed, the resource will automatically refetch the data
+ * const { data: accounts, refetch } = $resource(getAccounts, () => ({ recordId: accountId.value }));
+ *
+ * export { accounts, refetch };
+ *
+ * // Usage from a component
+ * import { LightningElement } from 'lwc';
+ * import { $computed } from 'c/signals';
+ * import { accounts, refetch } from 'c/myResource';
+ *
+ * export default class MyComponent extends LightningElement {
+ * accounts = $computed(() => this.accounts = accounts.value).value;
+ * }
+ *
+ * @param fn The function that will be called to fetch the data. Usually an Apex method but can be any async function.
+ * @param source The source object or function that will be used as the parameters for the fetch function
+ * @param options The options to configure the resource. Allows you to provide an initial value for the resource.
+ */
function $resource(fn, source, options) {
function loadingState(data) {
return {
@@ -77,6 +184,8 @@ function $resource(fn, source, options) {
let _value = options?.initialValue ?? null;
let _previousParams;
const _signal = $signal(loadingState(_value));
+ // Optimistic updates are enabled by default
+ const optimisticMutate = options?.optimisticMutate ?? true;
const execute = async () => {
_signal.value = loadingState(_value);
const derivedSource = source instanceof Function ? source() : source;
@@ -105,8 +214,31 @@ function $resource(fn, source, options) {
}
};
$effect(execute);
+ /**
+ * Callback function that updates the value of the resource.
+ * @param value The value we want to set the resource to.
+ * @param error An optional error object.
+ */
+ function mutatorCallback(value, error) {
+ _value = value;
+ _signal.value = {
+ data: value,
+ loading: false,
+ error: error ?? null
+ };
+ }
return {
data: _signal.readOnly,
+ mutate: (newValue) => {
+ const previousValue = _value;
+ if (optimisticMutate) {
+ // If optimistic updates are enabled, update the value immediately
+ mutatorCallback(newValue);
+ }
+ if (options?.onMutate) {
+ options.onMutate(newValue, previousValue, mutatorCallback);
+ }
+ },
refetch: async () => {
_isInitialLoad = true;
await execute();
diff --git a/force-app/lwc/signals/signals.js-meta.xml b/force-app/lwc/signals/signals.js-meta.xml
index 0488e66..48a3735 100644
--- a/force-app/lwc/signals/signals.js-meta.xml
+++ b/force-app/lwc/signals/signals.js-meta.xml
@@ -4,4 +4,4 @@
Signals
true
Signals
-
+
\ No newline at end of file
diff --git a/force-app/lwc/signals/use.js b/force-app/lwc/signals/use.js
index 1b874e4..3ce7421 100644
--- a/force-app/lwc/signals/use.js
+++ b/force-app/lwc/signals/use.js
@@ -1,52 +1,52 @@
export function createStorage(get, set) {
- return { get, set };
+ return { get, set };
}
export function useInMemoryStorage(value) {
- let _value = value;
- function getter() {
- return _value;
- }
- function setter(newValue) {
- _value = newValue;
- }
- return createStorage(getter, setter);
-}
-function useLocalStorageCreator(key, value) {
- function getter() {
- const item = localStorage.getItem(key);
- if (item) {
- return JSON.parse(item);
+ let _value = value;
+ function getter() {
+ return _value;
}
- return value;
- }
- function setter(newValue) {
- localStorage.setItem(key, JSON.stringify(newValue));
- }
- // Set initial value if not set
- if (!localStorage.getItem(key)) {
- localStorage.setItem(key, JSON.stringify(value));
- }
- return createStorage(getter, setter);
-}
-export function useLocalStorage(key) {
- return function (value) {
- return useLocalStorageCreator(key, value);
- };
+ function setter(newValue) {
+ _value = newValue;
+ }
+ return createStorage(getter, setter);
}
-export function useCookies(key, expires) {
- return function (value) {
+function useLocalStorageCreator(key, value) {
function getter() {
- const cookie = document.cookie
- .split("; ")
- .find((row) => row.startsWith(key));
- if (cookie) {
- return JSON.parse(cookie.split("=")[1]);
- }
- return value;
+ const item = localStorage.getItem(key);
+ if (item) {
+ return JSON.parse(item);
+ }
+ return value;
}
function setter(newValue) {
- document.cookie = `${key}=${JSON.stringify(newValue)}; expires=${expires?.toUTCString()}`;
+ localStorage.setItem(key, JSON.stringify(newValue));
+ }
+ // Set initial value if not set
+ if (!localStorage.getItem(key)) {
+ localStorage.setItem(key, JSON.stringify(value));
}
return createStorage(getter, setter);
- };
+}
+export function useLocalStorage(key) {
+ return function (value) {
+ return useLocalStorageCreator(key, value);
+ };
+}
+export function useCookies(key, expires) {
+ return function (value) {
+ function getter() {
+ const cookie = document.cookie
+ .split("; ")
+ .find((row) => row.startsWith(key));
+ if (cookie) {
+ return JSON.parse(cookie.split("=")[1]);
+ }
+ return value;
+ }
+ function setter(newValue) {
+ document.cookie = `${key}=${JSON.stringify(newValue)}; expires=${expires?.toUTCString()}`;
+ }
+ return createStorage(getter, setter);
+ };
}
diff --git a/package-lock.json b/package-lock.json
index a4a8ec7..3ed6e11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@salesforce/eslint-config-lwc": "^3.2.3",
"@salesforce/eslint-plugin-aura": "^2.0.0",
"@salesforce/eslint-plugin-lightning": "^1.0.0",
+ "@tailwindcss/forms": "^0.5.7",
"@types/jest": "^29.5.12",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.25.4",
@@ -26,12 +27,25 @@
"lint-staged": "^15.1.0",
"prettier": "^3.1.0",
"prettier-plugin-apex": "^2.0.1",
+ "tailwindcss": "^3.4.3",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.9.0"
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -763,6 +777,102 @@
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true
},
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1641,6 +1751,16 @@
"node": ">= 8"
}
},
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@prettier/plugin-xml": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-3.4.1.tgz",
@@ -1828,6 +1948,18 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
+ "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
+ "dev": true,
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -2415,6 +2547,12 @@
"node": ">=4"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -2787,6 +2925,18 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -2904,6 +3054,15 @@
"node": ">=6"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001620",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz",
@@ -2956,6 +3115,42 @@
"regexp-to-ast": "0.5.0"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
@@ -3282,6 +3477,18 @@
"node": ">= 8"
}
},
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
@@ -3475,6 +3682,12 @@
"node": ">=8"
}
},
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -3505,6 +3718,12 @@
"node": ">=8"
}
},
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -3530,6 +3749,12 @@
"node": ">=12"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.773",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz",
@@ -4627,6 +4852,34 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+ "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -5189,6 +5442,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -5557,6 +5822,24 @@
"node": ">=8"
}
},
+ "node_modules/jackspeak": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
+ "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
@@ -7140,6 +7423,15 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/jiti": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
+ "dev": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/joi": {
"version": "17.13.1",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz",
@@ -7752,6 +8044,15 @@
"node": ">=6"
}
},
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "dev": true,
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
@@ -7776,12 +8077,50 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -7827,6 +8166,24 @@
"integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==",
"dev": true
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -8067,6 +8424,31 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+ "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
+ "dev": true,
+ "engines": {
+ "node": "14 || >=16.14"
+ }
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -8106,6 +8488,15 @@
"node": ">=0.10"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/pirates": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
@@ -8188,17 +8579,154 @@
"node": ">= 0.4"
}
},
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prettier": {
- "version": "3.2.5",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
+ "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
@@ -8340,6 +8868,27 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true
},
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/regexp-to-ast": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
@@ -8711,6 +9260,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@@ -8787,6 +9345,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string-width/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -8875,6 +9463,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
@@ -8905,6 +9506,59 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz",
+ "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -8935,6 +9589,58 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
+ "node_modules/tailwindcss": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
+ "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
+ "dev": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/tailwindcss/node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -8977,6 +9683,27 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -9043,6 +9770,12 @@
"typescript": ">=4.2.0"
}
},
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true
+ },
"node_modules/ts-jest": {
"version": "29.1.2",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz",
@@ -9634,6 +10367,12 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -9813,6 +10552,86 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
diff --git a/package.json b/package.json
index 967752c..127ca09 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,13 @@
"test:coverage": "jest --coverage",
"create:scratch": "sf org create scratch --alias lwc-signals --definition-file config/project-scratch-def.json --set-default",
"dev:start": "npm run create:scratch && sf project deploy start",
- "lint": "eslint **/{aura,lwc}/**/*.js",
+ "lint": "eslint ./{examples,force-app,src}/**/{lwc}/*.{js,ts} --no-error-on-unmatched-pattern",
"prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
"prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"",
"postinstall": "husky install",
- "precommit": "lint-staged"
+ "precommit": "lint-staged",
+ "tw:build": "npx tailwindcss -i ./examples/tw/input.css -o ./examples/main/default/staticresources/tw/css/main.css --minify",
+ "tw:build:watch": "npx tailwindcss -i ./examples/tw/input.css -o ./examples/main/default/staticresources/tw/css/main.css --minify --watch"
},
"devDependencies": {
"@eslint/js": "^9.2.0",
@@ -31,17 +33,18 @@
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.1.0",
"prettier": "^3.1.0",
- "prettier-plugin-apex": "^2.0.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
- "typescript-eslint": "^7.9.0"
+ "typescript-eslint": "^7.9.0",
+ "tailwindcss": "^3.4.3",
+ "@tailwindcss/forms": "^0.5.7"
},
"lint-staged": {
- "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [
+ "**/*.{cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [
"prettier --write"
],
- "**/{aura,lwc}/**/*.js": [
+ "**/{lwc}/**/*.js": [
"eslint"
]
}
diff --git a/sfdx-project.json b/sfdx-project.json
index 7959923..c1780ba 100644
--- a/sfdx-project.json
+++ b/sfdx-project.json
@@ -14,10 +14,6 @@
}
],
"name": "lwc-signals",
- "namespace": "signal",
"sfdcLoginUrl": "https://login.salesforce.com",
- "sourceApiVersion": "60.0",
- "packageAliases": {
- "LWC Signals": "0HoRb000000004rKAA"
- }
+ "sourceApiVersion": "60.0"
}
diff --git a/src/lwc/signals/__tests__/signals.test.ts b/src/lwc/signals/__tests__/signals.test.ts
index f35d714..467b85e 100644
--- a/src/lwc/signals/__tests__/signals.test.ts
+++ b/src/lwc/signals/__tests__/signals.test.ts
@@ -22,6 +22,29 @@ describe("signals", () => {
expect(computed.value).toBe(2);
});
+ test("does not recompute when the same value is set", () => {
+ const signal = $signal(0);
+
+ let timesComputed = 0;
+ const computed = $computed(() => {
+ timesComputed++;
+ return signal.value * 2;
+ });
+
+ expect(computed.value).toBe(0);
+ expect(timesComputed).toBe(1);
+
+ signal.value = 1;
+
+ expect(computed.value).toBe(2);
+ expect(timesComputed).toBe(2);
+
+ signal.value = 1;
+
+ expect(computed.value).toBe(2);
+ expect(timesComputed).toBe(2);
+ });
+
test("can derive a computed value from another computed value", () => {
const signal = $signal(0);
const computed = $computed(() => signal.value * 2);
@@ -157,13 +180,12 @@ describe("signals", () => {
});
});
- test("can force a refetch of a resource", async () => {
- let counter = 0;
+ test("can mutate a resource", async () => {
const asyncFunction = async () => {
- return counter++;
+ return "done";
};
- const { data: resource, refetch } = $resource(asyncFunction);
+ const { data: resource, mutate } = $resource(asyncFunction);
expect(resource.value).toEqual({
data: null,
@@ -174,104 +196,244 @@ describe("signals", () => {
await new Promise(process.nextTick);
expect(resource.value).toEqual({
- data: 0,
+ data: "done",
loading: false,
error: null
});
- refetch();
+ mutate("mutated");
expect(resource.value).toEqual({
- data: 0,
- loading: true,
+ data: "mutated",
+ loading: false,
error: null
});
+ });
+
+ test("does not mutate a resource if optimistic updating is not turned on and no onMutate is provided", async () => {
+ const asyncFunction = async () => {
+ return "done";
+ };
+
+ const { data: resource, mutate } = $resource(asyncFunction, undefined, {
+ optimisticMutate: false
+ });
await new Promise(process.nextTick);
expect(resource.value).toEqual({
- data: 1,
+ data: "done",
+ loading: false,
+ error: null
+ });
+
+ mutate("mutated");
+
+ expect(resource.value).toEqual({
+ data: "done",
loading: false,
error: null
});
});
- test("can create custom storages", () => {
- const useUndo = (value: T) => {
- const _valueStack: T[] = [];
+ test("can react to a mutation", async () => {
+ const asyncFunction = async () => {
+ return "done";
+ };
- // add the initial value to the stack
- _valueStack.push(value);
+ let hasReacted = false;
+ const reactionFunction = () => {
+ hasReacted = true;
+ };
- function undo() {
- _valueStack.pop();
- }
+ const { mutate } = $resource(asyncFunction, undefined, {
+ onMutate: reactionFunction
+ });
- const customStorage = createStorage(
- () => {
- // Get value at the top of the stack
- return _valueStack[_valueStack.length - 1];
- },
- (newValue) => {
- _valueStack.push(newValue);
- },
- );
-
- return {
- ...customStorage,
- undo
- };
+ await new Promise(process.nextTick);
+
+ mutate("mutated");
+
+ await new Promise(process.nextTick);
+
+ expect(hasReacted).toBe(true);
+ });
+
+ test("can mutate a resource and change the value on success", async () => {
+ const asyncFunction = async () => {
+ return "done";
};
- const signal = $signal(0, {
- storage: useUndo
- }) as unknown as Signal & { undo: () => void };
+ const asyncReaction = async (newValue: string, __: string | null, mutate: (value: string | null, error?: unknown) => void) => {
+ mutate(`${newValue} - post async success`);
+ };
- expect(signal.value).toBe(0);
+ const { data: resource, mutate } = $resource(asyncFunction, undefined, {
+ onMutate: asyncReaction
+ });
- signal.value = 1;
- expect(signal.value).toBe(1);
+ await new Promise(process.nextTick);
- signal.value = 2;
- expect(signal.value).toBe(2);
+ expect(resource.value).toEqual({
+ data: "done",
+ loading: false,
+ error: null
+ });
- signal.undo();
- expect(signal.value).toBe(1);
+ mutate("mutated");
- signal.undo();
- expect(signal.value).toBe(0);
+ expect(resource.value).toEqual({
+ data: "mutated - post async success",
+ loading: false,
+ error: null
+ });
});
- });
- describe("storing values in local storage", () => {
- test("should have a default value", () => {
- const signal = $signal(0, {
- storage: useLocalStorage("test")
+ test('the onMutate function can set an error', async () => {
+ const asyncFunction = async () => {
+ return 'done';
+ };
+
+ const asyncReaction = async (newValue: string, _: string | null, mutate: (value: string | null, error?: unknown) => void) => {
+ mutate(null, 'An error occurred');
+ };
+
+ const { data: resource, mutate } = $resource(asyncFunction, undefined, {
+ onMutate: asyncReaction
+ });
+
+ await new Promise(process.nextTick);
+
+ expect(resource.value).toEqual({
+ data: 'done',
+ loading: false,
+ error: null
+ });
+
+ mutate('mutated');
+
+ expect(resource.value).toEqual({
+ data: null,
+ loading: false,
+ error: 'An error occurred'
});
- expect(signal.value).toBe(0);
});
+ });
- test("should update the value", () => {
- const signal = $signal(0);
- signal.value = 1;
- expect(signal.value).toBe(1);
+ test("can force a refetch of a resource", async () => {
+ let counter = 0;
+ const asyncFunction = async () => {
+ return counter++;
+ };
+
+ const { data: resource, refetch } = $resource(asyncFunction);
+
+ expect(resource.value).toEqual({
+ data: null,
+ loading: true,
+ error: null
+ });
+
+ await new Promise(process.nextTick);
+
+ expect(resource.value).toEqual({
+ data: 0,
+ loading: false,
+ error: null
+ });
+
+ refetch();
+
+ expect(resource.value).toEqual({
+ data: 0,
+ loading: true,
+ error: null
+ });
+
+ await new Promise(process.nextTick);
+
+ expect(resource.value).toEqual({
+ data: 1,
+ loading: false,
+ error: null
});
});
- describe("storing values in cookies", () => {
- test("should have a default value", () => {
- const signal = $signal(0, {
- storage: useCookies("test")
- });
- expect(signal.value).toBe(0);
+ test("can create custom storages", () => {
+ const useUndo = (value: T) => {
+ const _valueStack: T[] = [];
+
+ // add the initial value to the stack
+ _valueStack.push(value);
+
+ function undo() {
+ _valueStack.pop();
+ }
+
+ const customStorage = createStorage(
+ () => {
+ // Get value at the top of the stack
+ return _valueStack[_valueStack.length - 1];
+ },
+ (newValue) => {
+ _valueStack.push(newValue);
+ }
+ );
+
+ return {
+ ...customStorage,
+ undo
+ };
+ };
+
+ const signal = $signal(0, {
+ storage: useUndo
+ }) as unknown as Signal & { undo: () => void };
+
+ expect(signal.value).toBe(0);
+
+ signal.value = 1;
+ expect(signal.value).toBe(1);
+
+ signal.value = 2;
+ expect(signal.value).toBe(2);
+
+ signal.undo();
+ expect(signal.value).toBe(1);
+
+ signal.undo();
+ expect(signal.value).toBe(0);
+ });
+});
+
+describe("storing values in local storage", () => {
+ test("should have a default value", () => {
+ const signal = $signal(0, {
+ storage: useLocalStorage("test")
});
+ expect(signal.value).toBe(0);
+ });
- test("should update the value", () => {
- const signal = $signal(0, {
- storage: useCookies("test")
- });
- signal.value = 1;
- expect(signal.value).toBe(1);
+ test("should update the value", () => {
+ const signal = $signal(0);
+ signal.value = 1;
+ expect(signal.value).toBe(1);
+ });
+});
+
+describe("storing values in cookies", () => {
+ test("should have a default value", () => {
+ const signal = $signal(0, {
+ storage: useCookies("test")
+ });
+ expect(signal.value).toBe(0);
+ });
+
+ test("should update the value", () => {
+ const signal = $signal(0, {
+ storage: useCookies("test")
});
+ signal.value = 1;
+ expect(signal.value).toBe(1);
});
});
diff --git a/src/lwc/signals/core.ts b/src/lwc/signals/core.ts
index 7b3dabc..a20f346 100644
--- a/src/lwc/signals/core.ts
+++ b/src/lwc/signals/core.ts
@@ -16,6 +16,25 @@ function _getCurrentObserver(): VoidFunction | undefined {
return context[context.length - 1];
}
+/**
+ * Creates a new effect that will be executed immediately and whenever
+ * any of the signals it reads from change.
+ *
+ * Avoid changing $signal values inside an effect, as it can lead to
+ * infinite loops.
+ *
+ * ```javascript
+ * import { $signal, $effect } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * $effect(() => {
+ * console.log(count.value);
+ * });
+ * ```
+ *
+ * @param fn The function to execute
+ */
function $effect(fn: VoidFunction): void {
const execute = () => {
context.push(execute);
@@ -31,14 +50,31 @@ function $effect(fn: VoidFunction): void {
type ComputedFunction = () => T;
+/**
+ * Creates a new computed value that will be updated whenever the signals
+ * it reads from change. Returns a read-only signal that contains the
+ * computed value.
+ *
+ * ```javascript
+ * import { $signal, $computed } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * const double = $computed(() => count.value * 2);
+ * ```
+ *
+ * @param fn The function that returns the computed value.
+ */
function $computed(fn: ComputedFunction): ReadOnlySignal {
- const computedSignal: Signal = $signal(fn());
+ // The initial value is undefined, as it will be computed
+ // when the effect runs for the first time
+ const computedSignal: Signal = $signal(undefined);
$effect(() => {
computedSignal.value = fn();
});
- return computedSignal.readOnly;
+ return computedSignal.readOnly as ReadOnlySignal;
}
type StorageFn = (value: T) => State & { [key: string]: unknown };
@@ -47,9 +83,32 @@ type SignalOptions = {
storage: StorageFn
};
+/**
+ * Creates a new signal with the provided value. A signal is a reactive
+ * primitive that can be used to store and update values. Signals can be
+ * read and written to, and can be used to create computed values or
+ * can be read from within an effect.
+ *
+ * You can read the current value of a signal by accessing the `value` property.
+ *
+ * ```javascript
+ * import { $signal } from 'c/signals';
+ *
+ * const count = $signal(0);
+ *
+ * // Read the current value, logs 0
+ * console.log(count.value);
+ *
+ * // Update the value
+ * count.value = 1;
+ * ```
+ *
+ * @param value The initial value of the signal
+ * @param options Options to configure the signal
+ */
function $signal(value: T, options: SignalOptions = {
storage: useInMemoryStorage
-}): Signal & Omit>, 'get' | 'set'> {
+}): Signal & Omit>, "get" | "set"> {
const _storageOption: State = options.storage(value);
const subscribers: Set = new Set();
@@ -62,7 +121,7 @@ function $signal(value: T, options: SignalOptions = {
}
function setter(newValue: T) {
- if (newValue === _storageOption) {
+ if (newValue === _storageOption.get()) {
return;
}
_storageOption.set(newValue);
@@ -71,7 +130,7 @@ function $signal(value: T, options: SignalOptions = {
}
}
- const returnValue: Signal & Omit>, 'get' | 'set'> = {
+ const returnValue: Signal & Omit>, "get" | "set"> = {
..._storageOption,
get value() {
return getter();
@@ -104,15 +163,71 @@ type AsyncData = {
type ResourceResponse = {
data: ReadOnlySignal>;
+ mutate: (newValue: T) => void;
refetch: () => void;
};
type UnknownArgsMap = { [key: string]: unknown };
+type MutatorCallback = (value: T | null, error?: unknown) => void;
+
+type OnMutate = (newValue: T, oldValue: T | null, mutate: MutatorCallback) => Promise | void;
+
type ResourceOptions = {
initialValue?: T;
+ optimisticMutate?: boolean;
+ onMutate?: OnMutate;
+ storage?: StorageFn;
};
+/**
+ * Creates a new resource that fetches data from an async source. The resource
+ * will automatically fetch the data when the component is mounted.
+ *
+ * It receives a function that returns a promise, which will be called to fetch
+ * the data. Optionally, you can provide a source object or function that will
+ * be used as the parameters for the fetch function.
+ *
+ * If a function that contain $computed values is provided as the source, the
+ * resource will automatically refetch the data when the computed value changes.
+ *
+ * `$resource` returns an object with 2 properties:
+ * - `data`: a signal that contains the current state of the resource. It has
+ * the following shape:
+ * ```javascript
+ * {
+ * data: T | null;
+ * loading: boolean;
+ * error: unknown | null;
+ * }
+ * ```
+ *
+ * - `refetch`: a function that can be called to force refetch the data.
+ *
+ * ```javascript
+ * import { $signal, $resource } from 'c/signals';
+ * import getAccounts from '@salesforce/apex/AccountController.getAccounts';
+ *
+ * const accountId = $signal('00B5e00000Dv9ZCEAZ');
+ *
+ * // If the account Id value is changed, the resource will automatically refetch the data
+ * const { data: accounts, refetch } = $resource(getAccounts, () => ({ recordId: accountId.value }));
+ *
+ * export { accounts, refetch };
+ *
+ * // Usage from a component
+ * import { LightningElement } from 'lwc';
+ * import { $computed } from 'c/signals';
+ * import { accounts, refetch } from 'c/myResource';
+ *
+ * export default class MyComponent extends LightningElement {
+ * accounts = $computed(() => this.accounts = accounts.value).value;
+ * }
+ *
+ * @param fn The function that will be called to fetch the data. Usually an Apex method but can be any async function.
+ * @param source The source object or function that will be used as the parameters for the fetch function
+ * @param options The options to configure the resource. Allows you to provide an initial value for the resource.
+ */
function $resource(
fn: (params?: { [key: string]: unknown }) => Promise,
source?: UnknownArgsMap | (() => UnknownArgsMap),
@@ -130,6 +245,8 @@ function $resource(
let _value: T | null = options?.initialValue ?? null;
let _previousParams: UnknownArgsMap | undefined;
const _signal = $signal>(loadingState(_value));
+ // Optimistic updates are enabled by default
+ const optimisticMutate = options?.optimisticMutate ?? true;
const execute = async () => {
_signal.value = loadingState(_value);
@@ -165,8 +282,33 @@ function $resource(
$effect(execute);
+ /**
+ * Callback function that updates the value of the resource.
+ * @param value The value we want to set the resource to.
+ * @param error An optional error object.
+ */
+ function mutatorCallback(value: T | null, error?: unknown): void {
+ _value = value;
+ _signal.value = {
+ data: value,
+ loading: false,
+ error: error ?? null
+ };
+ }
+
return {
data: _signal.readOnly,
+ mutate: (newValue: T) => {
+ const previousValue = _value;
+ if (optimisticMutate) {
+ // If optimistic updates are enabled, update the value immediately
+ mutatorCallback(newValue);
+ }
+
+ if (options?.onMutate) {
+ options.onMutate(newValue, previousValue, mutatorCallback);
+ }
+ },
refetch: async () => {
_isInitialLoad = true;
await execute();
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..cfd9407
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ["./examples/**/*.{html,js}"],
+ important: true,
+ theme: {
+ extend: {}
+ },
+ plugins: []
+};